Libraries¶
#Instalar librerias
import importlib.util
import sys
def check_and_install_library(library_name_list: list):
for library_name in library_name_list:
spec = importlib.util.find_spec(library_name)
if spec is None:
print(f"Library '{library_name}' not found. Installing...")
try:
# Use pip to install the library
# The ! prefix runs shell commands from within Jupyter
!{sys.executable} -m pip install {library_name}
print(f"Library '{library_name}' installed successfully.")
except Exception as e:
print(f"Error installing '{library_name}': {e}")
# else:
# print(f"Library '{library_name}' is already installed.")
return
library_name_list = ['pandas', 'numpy', 'jupyter', 'notebook', 'yfinance', 'matplotlib.pyplot', 'json', 'ipynb', 'import_ipynb', 'datetime',
'ipywidgets', 'IPython.display', 'anywidget',# widgets
'nbconvert', 'pandoc', 'TeX'] #To export to HTML and PDF
check_and_install_library(library_name_list)
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import ipywidgets as widgets
from IPython.display import display, clear_output
#from datetime import date
from datetime import datetime
import json
#import ipynb
from dataclasses import dataclass
import nbconvert
import import_ipynb
import functions # => .../functions.ipynb file attached
importlib.reload(functions) # Reloads the module
from plotly.graph_objects import FigureWidget
%reload_ext autoreload
%autoreload 2
Library 'TeX' not found. Installing... Requirement already satisfied: TeX in c:\users\egarcia\appdata\local\programs\python\python312\lib\site-packages (1.8) Library 'TeX' installed successfully.
Dates¶
#Dates
global start_date
start_date = datetime(2021, 4, 25) # <- date in which brokerage account started
today = datetime.today()
today = today.replace(hour=0, minute=0, second=0, microsecond=0)
no_days = today - start_date
print(f" \
Start Date: {start_date},\n \
End Date: {today},\n \
number of days {no_days}")
Start Date: 2021-04-25 00:00:00,
End Date: 2026-01-06 00:00:00,
number of days 1717 days, 0:00:00
Tickers¶
Background: From an initial list of 45 assets (tickers) choose a number greather than 5 shuch that they are diversified among industries, liquidity, heterogeneousity, etc.
risk_free = 0.04152 #10-year treasury
# Open list of tickers from original Robinhood's file_df_df
file_name = "Robinhood.xlsx" # Enrique
# file_df = "App_GBM_Detalle_Portafolio_USA_1763936603841.xlsx" # Beto
file_df = pd.read_excel(file_name)
tickers = file_df[["Ticker"]]
tickers = list(tickers["Ticker"])
no_assets = len(tickers)
print(f"Tickers: {tickers}")
print(f"Number of Assets: {no_assets}")
Tickers: ['QYLD', 'NVDA', 'PBR', 'PLTR', 'MSFT', 'AAPL', 'NU', 'DIS', 'VOOV', 'TSM', 'GLD', 'QQQM', 'VOOG', 'KO', 'SOFI', 'QSR', 'UNH', 'TSLA', 'SPYD', 'SERV', 'SOXX', 'VOO', 'OMAB', 'VYM', 'VGT', 'GOOGL', 'CME', 'GOOG', 'META', 'BAC', 'CRWD', 'NFLX', 'MCD', 'SPYG', 'VUG', 'ADBE', 'MAR', 'VTV', 'CAT', 'LMT', 'ASML', 'BRK-B', 'SPG', 'PSA', 'HD'] Number of Assets: 45
Fundamental Analysis ALL Tickers¶
Criteria to choose stocks:
|Ratio | Formula | Criteria (great if) |Attribute :--- | :---: | :---: |:---: P/E Ratio |Current Stock Price / Earnings per Share | between 10 and 20 (fair valuation) |info.trailingPE P/B Ratio |Price per Share / Book Value | < 3 (not overvalued) |info.priceToBook ROIC (%) |NOPAT / Total Inv. Capital (=Debt+Equity-Assets) | > 15% |functions.get_roic('AAPL') D/E (%) |Debt / Equity | < 100% (0%-200%) |info.debtToEquity EPS (USD) |Net Income / Shares Outstanding | > 10% CAGR |info.epsForward ROE Ratio | Net Income / Equity | > 0.15 |info.returnOnEquity EBIT Margin (%) |EBIT / Sales | > 10% |functions.get_ebit_margin("AAPL") Gross Margin Ratio |Sales - COGS / Sales | > 0.40 (0.35-0.65) |info.grossMargins Net Margin (%) |Net Income / Revenue | (15%-25%) |functions.get_net_margin("AAPL") Current Ratio |Current Assets / Current Liabilities | (1.5-2.0) |info.currentRatio Earning Growth Ratio (PEG Ratio) | P/E Ratio / Annual EPS Growth Rate (% as a whole number) | < 1.0 (undervalued) | info.earningsGrowth
ROIC: Return on Invested Capital
COGS: Cost of Goods Sold
CAGR: Compound Annual Growth Rate
NOPAT: Net Operating Profit After Tax (NOPAT) = Operating Income(or Operating Profit) * (1 - Tax Rate)
Book Value: = (Total Assets - Total Liabilities - Preferred Stock) / Number of Outstanding Common Shares
Overall Risk: Overall assessment including Audit Risk, Board Risk, Compensation Risk, Share Holder Rights Risk. 10 is max, 0 is min. Can be seen individually in .info
yfinance provides:
- hist()
- info
- Income Statement
- Financial Statement
## Fundamental Analysis for all Tickers based on Financial Ratios
# Call the get_fundamental_analysis() function (...be patient takes ~71sec)
fa_df = functions.get_fundamental_analysis(tickers, start_date, today, showLogs = "no")
display(fa_df) #Result filtered by: ['Sector','P/E Ratio','P/B Ratio']
# sys.stdout = original_stdout
| Name | Sector | Industry | CAGR_% | P/E Ratio | P/B Ratio | ROIC_% | D/E_% | EPS_usd | ROE Ratio | ... | Gross Margin_ratio | Net Margin_% | Current Ratio | Overall Risk | Beta | EBITDA_usd | EBITDA Margins Ratio | Earning Growth Ratio | Revenue Growth Ratio | Operating Margins Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Ticker | |||||||||||||||||||||
| NFLX | Netflix, Inc. | Communication Services | Entertainment | 13.21 | 38.108330 | 14.932244 | 28.58 | 65.822 | 3.242550 | 0.42861 | ... | 0.48085 | 22.34 | 1.332 | 9.0 | 1.711 | 1.296965e+10 | 0.29899 | 0.087 | 0.172 | 0.28220 |
| GOOG | Alphabet Inc. | Communication Services | Internet Content & Information | 23.97 | 31.355732 | 9.906034 | 30.69 | 11.424 | 11.197380 | 0.35450 | ... | 0.59172 | 28.60 | 1.747 | 0.0 | 1.086 | 1.451740e+11 | 0.37661 | 0.353 | 0.159 | 0.30512 |
| GOOGL | Alphabet Inc. | Communication Services | Internet Content & Information | 24.10 | 31.247780 | 9.881684 | 30.69 | 11.424 | 11.197380 | 0.35450 | ... | 0.59172 | 28.60 | 1.747 | 10.0 | 1.086 | 1.451740e+11 | 0.37661 | 0.353 | 0.159 | 0.30512 |
| META | Meta Platforms, Inc. | Communication Services | Internet Content & Information | 18.12 | 29.149998 | 8.557937 | 33.52 | 26.311 | 30.418530 | 0.32643 | ... | 0.82013 | 37.91 | 1.978 | 10.0 | 1.287 | 9.839900e+10 | 0.51937 | -0.826 | 0.262 | 0.40075 |
| DIS | The Walt Disney Company | Communication Services | Entertainment | -9.24 | 16.628279 | 1.859483 | 7.32 | 39.632 | 7.355800 | 0.12203 | ... | 0.37764 | 13.14 | 0.710 | 3.0 | 1.442 | 1.941900e+10 | 0.20566 | 1.873 | -0.005 | 0.11868 |
| TSLA | Tesla, Inc. | Consumer Cyclical | Auto Manufacturers | 13.78 | 311.496550 | 18.774212 | 10.61 | 17.082 | 2.203830 | 0.06791 | ... | 0.17007 | 7.30 | 2.066 | 10.0 | 1.835 | 1.076800e+10 | 0.11260 | -0.371 | 0.116 | 0.06628 |
| MAR | Marriott International, Inc. | Consumer Cyclical | Lodging | 17.93 | 32.774500 | -26.863880 | 24.47 | 0.000 | 11.383310 | 0.00000 | ... | 0.81554 | 9.46 | 0.467 | 7.0 | 1.157 | 4.600000e+09 | 0.66919 | 0.290 | 0.056 | 0.65934 |
| MCD | McDonald's Corporation | Consumer Cyclical | Restaurants | 8.03 | 25.585323 | -98.735590 | 20.02 | 0.000 | 13.229860 | 0.00000 | ... | 0.57425 | 31.72 | 1.000 | 5.0 | 0.531 | 1.429200e+10 | 0.54417 | 0.016 | 0.030 | 0.46906 |
| QSR | Restaurant Brands International Inc. | Consumer Cyclical | Restaurants | 3.70 | 23.666666 | 6.470816 | 10.81 | 306.718 | 4.015010 | 0.25245 | ... | 0.33528 | 12.15 | 1.059 | 4.0 | 0.605 | 2.701000e+09 | 0.29156 | 0.217 | 0.069 | 0.27726 |
| HD | The Home Depot, Inc. | Consumer Cyclical | Home Improvement Retail | 4.08 | 23.471350 | 28.257370 | 24.64 | 544.586 | 15.095210 | 1.62909 | ... | 0.33355 | 9.28 | 1.051 | 1.0 | 1.072 | 2.558700e+10 | 0.15396 | -0.014 | 0.028 | 0.12945 |
| KO | The Coca-Cola Company | Consumer Defensive | Beverages - Non-Alcoholic | 8.37 | 22.496689 | 9.354261 | 20.50 | 144.771 | 3.220510 | 0.42442 | ... | 0.61633 | 22.59 | 1.211 | 3.0 | 0.387 | 1.630700e+10 | 0.34213 | 0.301 | 0.051 | 0.32373 |
| SOXX | iShares Semiconductor ETF | ETF, others | ETF, others | 18.82 | 41.787422 | 0.749979 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VGT | Vanguard Information Technology Index Fund ETF... | ETF, others | ETF, others | 16.07 | 39.488186 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VUG | Vanguard Growth Index Fund ETF Shares | ETF, others | ETF, others | 13.41 | 39.276190 | 2.263974 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VOOG | Vanguard S&P 500 Growth Index Fund ETF Shares | ETF, others | ETF, others | 13.71 | 35.788900 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| QYLD | Global X NASDAQ 100 Covered Call ETF | ETF, others | ETF, others | 7.28 | 34.672573 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| QQQM | Invesco NASDAQ 100 ETF | ETF, others | ETF, others | 14.21 | 34.027912 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| SPYG | State Street SPDR Portfolio S&P 500 Growth ETF | ETF, others | ETF, others | 13.77 | 33.924810 | 1.701520 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VOO | Vanguard S&P 500 ETF | ETF, others | ETF, others | 12.82 | 29.083656 | 1.618149 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VOOV | Vanguard S&P 500 Value Index Fund ETF Shares | ETF, others | ETF, others | 10.85 | 23.608147 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VTV | Vanguard Value Index Fund ETF Shares | ETF, others | ETF, others | 10.73 | 21.379675 | 2.568654 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| VYM | Vanguard High Dividend Yield Index Fund ETF Sh... | ETF, others | ETF, others | 10.95 | 20.940632 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| SPYD | State Street SPDR Portfolio S&P 500 High Divid... | ETF, others | ETF, others | 6.48 | 15.968643 | 0.000000 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| GLD | SPDR Gold Shares | ETF, others | ETF, others | 20.98 | 0.000000 | 2.404230 | 0.00 | 0.000 | 0.000000 | 0.00000 | ... | 0.00000 | 0.00 | 0.000 | 0.0 | 0.000 | 0.000000e+00 | 0.00000 | 0.000 | 0.000 | 0.00000 |
| PBR | Petróleo Brasileiro S.A. - Petrobras | Energy | Oil & Gas Integrated | 36.76 | 5.435185 | 1.940786 | 8.81 | 88.498 | 2.536670 | 0.19022 | ... | 0.48152 | 8.23 | 0.819 | 0.0 | -0.032 | 1.910370e+11 | 0.38872 | 0.005 | -0.013 | 0.36224 |
| SOFI | SoFi Technologies, Inc. | Financial Services | Credit Services | 12.71 | 52.285717 | 4.017012 | 0.00 | 31.999 | 0.572950 | 0.08593 | ... | 0.82510 | 19.09 | 1.150 | 9.0 | 1.932 | 0.000000e+00 | 0.00000 | 1.059 | 0.378 | 0.15598 |
| NU | Nu Holdings Ltd. | Financial Services | Banks - Regional | 12.45 | 34.500004 | 8.236915 | 0.00 | 0.000 | 0.911740 | 0.27800 | ... | 0.00000 | 23.85 | 0.000 | 0.0 | 1.083 | 0.000000e+00 | 0.00000 | 0.409 | 0.363 | 0.58215 |
| CME | CME Group Inc. | Financial Services | Financial Data & Stock Exchanges | 10.76 | 26.678951 | 3.511150 | 13.50 | 13.372 | 11.683760 | 0.13346 | ... | 1.00000 | 57.52 | 1.021 | 10.0 | 0.291 | 4.494100e+09 | 0.70383 | -0.004 | -0.030 | 0.63386 |
| BRK-B | Berkshire Hathaway Inc. | Financial Services | Insurance - Diversified | 13.85 | 15.947536 | 0.001027 | 12.90 | 18.166 | 24.190810 | 0.10170 | ... | 0.24361 | 20.98 | 2.722 | 10.0 | 0.710 | 1.038640e+11 | 0.27911 | 0.172 | 0.021 | 0.41103 |
| BAC | Bank of America Corporation | Financial Services | Banks - Diversified | 10.81 | 15.543715 | 1.499038 | 0.00 | 0.000 | 4.357060 | 0.09871 | ... | 0.00000 | 26.63 | 0.000 | 4.0 | 1.295 | 0.000000e+00 | 0.00000 | 0.315 | 0.126 | 0.35293 |
| UNH | UnitedHealth Group Incorporated | Healthcare | Healthcare Plans | -1.44 | 17.822823 | 3.234996 | 12.62 | 75.734 | 17.767490 | 0.17476 | ... | 0.19701 | 3.60 | 0.823 | 8.0 | 0.425 | 2.924200e+10 | 0.06720 | -0.602 | 0.122 | 0.03813 |
| CAT | Caterpillar Inc. | Industrials | Farm & Heavy Construction Machinery | 25.49 | 31.643555 | 13.956912 | 21.86 | 201.046 | 22.386720 | 0.46283 | ... | 0.30120 | 16.65 | 1.384 | 4.0 | 1.568 | 1.395800e+10 | 0.21583 | -0.036 | 0.095 | 0.17746 |
| LMT | Lockheed Martin Corporation | Industrials | Aerospace & Defense | 10.00 | 28.483854 | 19.035872 | 25.81 | 358.987 | 29.223770 | 0.62776 | ... | 0.08252 | 7.51 | 1.129 | 5.0 | 0.245 | 7.257000e+09 | 0.09894 | 0.022 | 0.088 | 0.11693 |
| OMAB | Grupo Aeroportuario del Centro Norte, S.A.B. d... | Industrials | Airports & Air Services | 24.74 | 17.794788 | 75.197770 | 29.05 | 132.873 | 7.859000 | 0.54332 | ... | 0.74202 | 32.70 | 1.138 | 0.0 | 0.611 | 9.810583e+09 | 0.61446 | 0.092 | 0.061 | 0.61165 |
| SERV | Serve Robotics Inc. | Industrials | Specialty Industrial Machinery | -12.53 | 0.000000 | 3.024809 | -355.90 | 1.461 | -1.773330 | -0.47177 | ... | 0.00000 | -2162.29 | 17.214 | 10.0 | 0.000 | -8.331123e+07 | 0.00000 | 0.000 | 2.095 | -50.67831 |
| PSA | Public Storage | Real Estate | REIT - Industrial | 3.31 | 27.092419 | 9.225600 | 12.74 | 106.758 | 10.247400 | 0.19933 | ... | 0.72701 | 44.13 | 0.269 | 5.0 | 0.991 | 3.379439e+09 | 0.70476 | 0.213 | 0.031 | 0.46947 |
| SPG | Simon Property Group, Inc. | Real Estate | REIT - Retail | 15.45 | 26.653566 | 25.888590 | 13.78 | 884.863 | 6.882500 | 0.82455 | ... | 0.81993 | 39.75 | 0.592 | 10.0 | 1.400 | 4.549947e+09 | 0.73919 | 0.274 | 0.082 | 0.50757 |
| PLTR | Palantir Technologies Inc. | Technology | Software - Infrastructure | 52.23 | 395.545440 | 62.943940 | 9.44 | 3.520 | 1.010140 | 0.19504 | ... | 0.80808 | 16.13 | 6.427 | 10.0 | 1.545 | 8.757970e+08 | 0.22478 | 2.000 | 0.628 | 0.33296 |
| NVDA | NVIDIA Corporation | Technology | Semiconductors | 70.14 | 46.564354 | 38.454617 | 90.19 | 9.102 | 7.566360 | 1.07359 | ... | 0.70050 | 55.85 | 4.468 | 8.0 | 2.314 | 1.126960e+11 | 0.60220 | 0.667 | 0.625 | 0.63169 |
| ASML | ASML Holding N.V. | Technology | Semiconductor Equipment & Materials | 14.83 | 43.291855 | 21.398302 | 79.56 | 14.240 | 30.810055 | 0.53852 | ... | 0.52711 | 26.79 | 1.308 | 0.0 | 1.341 | 1.215790e+10 | 0.37743 | 0.038 | 0.007 | 0.32842 |
| AAPL | Apple Inc. | Technology | Consumer Electronics | 16.30 | 35.825737 | 53.548386 | 82.29 | 152.411 | 9.155080 | 1.71422 | ... | 0.46905 | 26.92 | 0.893 | 1.0 | 1.093 | 1.447480e+11 | 0.34782 | 0.912 | 0.079 | 0.31647 |
| MSFT | Microsoft Corporation | Technology | Software - Infrastructure | 14.36 | 33.678776 | 9.681614 | 27.78 | 33.154 | 18.742380 | 0.32241 | ... | 0.68764 | 36.15 | 1.401 | 5.0 | 1.073 | 1.664370e+11 | 0.56647 | 0.127 | 0.184 | 0.48873 |
| TSM | Taiwan Semiconductor Manufacturing Company Lim... | Technology | Semiconductors | 25.15 | 33.324715 | 52.480240 | 36.86 | 20.436 | 13.083090 | 0.34657 | ... | 0.58976 | 40.02 | 2.693 | 0.0 | 1.274 | 2.484714e+12 | 0.68423 | 0.391 | 0.303 | 0.50578 |
| ADBE | Adobe Inc. | Technology | Software - Application | -8.96 | 19.842012 | 11.895383 | 45.27 | 57.197 | 26.340080 | 0.55426 | ... | 0.89268 | 25.85 | 0.996 | 1.0 | 1.526 | 9.551333e+09 | 0.40184 | 0.172 | 0.105 | 0.36503 |
| CRWD | CrowdStrike Holdings, Inc. | Technology | Software - Infrastructure | 16.29 | 0.000000 | 28.652567 | -25.06 | 20.154 | 4.834480 | -0.08815 | ... | 0.74277 | -0.49 | 1.811 | 10.0 | 1.029 | -9.382400e+07 | -0.02055 | 0.000 | 0.222 | -0.05589 |
45 rows × 21 columns
ratio_criteria= {
'CAGR_%': (">", 10.0), # %
'P/E Ratio': ("between", (10.0, 20.0)), # ratio
'P/B Ratio': ("<", 3.0), # ratio
'ROIC_%': (">", 15.0), # %
'D/E_%': ("<", 100.0), # %
'EPS_usd': (">", 10.0), # USD. # 10% of asset's CAGR. Will automatically trigger 10% CAGR logic in the function
'ROE Ratio': (">", 0.15), # ratio
'EBIT Margin_%': (">", 10.0), # %
'Gross Margin_ratio': (">", 0.40), # ratio
'Net Margin_%': (">", 15.0), # %
'Current Ratio': (">", 1.5), # ratio
'Overall Risk': ("<", 5.0), # 10 is max, 0 is min.
'Beta': ("between", (0.0, 1.0)),
'EBITDA_usd': (">", 0.00), # USD
'EBITDA Margins Ratio': (">", 0.15), # ratio
'Earning Growth Ratio': ("<", 1.0), # ratio - PEG < 1.0 (Undervalued): This is the ideal range, as it suggests the stock price is low relative to its earnings growth potential, indicating a potentially attractive investment opportunity
'Revenue Growth Ratio': (">", 0.15), # ratio
'Operating Margins Ratio': (">", 0.15) # ratio
}
# Plot financial Ratios
functions.plot_ratios(fa_df, ratio_criteria, plots_per_row = 3)
# Generate the data
scorecard = functions.generate_scorecard(fa_df, ratio_criteria)
# Display with a gradient highlight on the Score column
print("\n--- STOCK SELECTION SCORECARD ---")
display(scorecard.style.background_gradient(subset=['Score %'], cmap='RdYlGn'))
--- STOCK SELECTION SCORECARD ---
| CAGR_% | P/E Ratio | P/B Ratio | ROIC_% | D/E_% | EPS_usd | ROE Ratio | EBIT Margin_% | Gross Margin_ratio | Net Margin_% | Current Ratio | Overall Risk | Beta | EBITDA_usd | EBITDA Margins Ratio | Earning Growth Ratio | Revenue Growth Ratio | Operating Margins Ratio | Score % | Name | Sector | Industry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Ticker | ||||||||||||||||||||||
| GOOG | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 83.330000 | Alphabet Inc. | Communication Services | Internet Content & Information |
| TSM | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 83.330000 | Taiwan Semiconductor Manufacturing Company Limited | Technology | Semiconductors |
| META | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 77.780000 | Meta Platforms, Inc. | Communication Services | Internet Content & Information |
| OMAB | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 77.780000 | Grupo Aeroportuario del Centro Norte, S.A.B. de C.V. | Industrials | Airports & Air Services |
| GOOGL | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 77.780000 | Alphabet Inc. | Communication Services | Internet Content & Information |
| NVDA | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 77.780000 | NVIDIA Corporation | Technology | Semiconductors |
| BRK-B | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 72.220000 | Berkshire Hathaway Inc. | Financial Services | Insurance - Diversified |
| ASML | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 72.220000 | ASML Holding N.V. | Technology | Semiconductor Equipment & Materials |
| MSFT | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 72.220000 | Microsoft Corporation | Technology | Software - Infrastructure |
| NFLX | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 72.220000 | Netflix, Inc. | Communication Services | Entertainment |
| ADBE | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 72.220000 | Adobe Inc. | Technology | Software - Application |
| KO | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 66.670000 | The Coca-Cola Company | Consumer Defensive | Beverages - Non-Alcoholic |
| MCD | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 66.670000 | McDonald's Corporation | Consumer Cyclical | Restaurants |
| AAPL | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 66.670000 | Apple Inc. | Technology | Consumer Electronics |
| CAT | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 61.110000 | Caterpillar Inc. | Industrials | Farm & Heavy Construction Machinery |
| CME | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 61.110000 | CME Group Inc. | Financial Services | Financial Data & Stock Exchanges |
| PBR | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 61.110000 | Petróleo Brasileiro S.A. - Petrobras | Energy | Oil & Gas Integrated |
| MAR | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 61.110000 | Marriott International, Inc. | Consumer Cyclical | Lodging |
| PLTR | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | 61.110000 | Palantir Technologies Inc. | Technology | Software - Infrastructure |
| PSA | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 55.560000 | Public Storage | Real Estate | REIT - Industrial |
| SPG | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | 55.560000 | Simon Property Group, Inc. | Real Estate | REIT - Retail |
| BAC | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | 50.000000 | Bank of America Corporation | Financial Services | Banks - Diversified |
| QSR | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 50.000000 | Restaurant Brands International Inc. | Consumer Cyclical | Restaurants |
| DIS | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | 44.440000 | The Walt Disney Company | Communication Services | Entertainment |
| HD | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | 44.440000 | The Home Depot, Inc. | Consumer Cyclical | Home Improvement Retail |
| NU | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | 44.440000 | Nu Holdings Ltd. | Financial Services | Banks - Regional |
| UNH | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | 38.890000 | UnitedHealth Group Incorporated | Healthcare | Healthcare Plans |
| CRWD | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | 38.890000 | CrowdStrike Holdings, Inc. | Technology | Software - Infrastructure |
| LMT | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | 38.890000 | Lockheed Martin Corporation | Industrials | Aerospace & Defense |
| VGT | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard Information Technology Index Fund ETF Shares | ETF, others | ETF, others |
| SOXX | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | iShares Semiconductor ETF | ETF, others | ETF, others |
| TSLA | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Tesla, Inc. | Consumer Cyclical | Auto Manufacturers |
| VYM | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard High Dividend Yield Index Fund ETF Shares | ETF, others | ETF, others |
| SPYG | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | State Street SPDR Portfolio S&P 500 Growth ETF | ETF, others | ETF, others |
| SOFI | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | 33.330000 | SoFi Technologies, Inc. | Financial Services | Credit Services |
| SPYD | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | State Street SPDR Portfolio S&P 500 High Dividend ETF | ETF, others | ETF, others |
| GLD | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | SPDR Gold Shares | ETF, others | ETF, others |
| VOOV | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard S&P 500 Value Index Fund ETF Shares | ETF, others | ETF, others |
| QQQM | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Invesco NASDAQ 100 ETF | ETF, others | ETF, others |
| VOOG | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard S&P 500 Growth Index Fund ETF Shares | ETF, others | ETF, others |
| VUG | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard Growth Index Fund ETF Shares | ETF, others | ETF, others |
| VTV | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard Value Index Fund ETF Shares | ETF, others | ETF, others |
| VOO | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 33.330000 | Vanguard S&P 500 ETF | ETF, others | ETF, others |
| QYLD | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 27.780000 | Global X NASDAQ 100 Covered Call ETF | ETF, others | ETF, others |
| SERV | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | 27.780000 | Serve Robotics Inc. | Industrials | Specialty Industrial Machinery |
# Sectors and Industries
functions.print_sector_industry(fa_df)
# --- SCORE vs BETA by Sector ---
functions.plot_risk_reward(fa_df, ratio_criteria)
# --- TOP N assets in all sectors ---
top_N_data = functions.get_top_N_assets(fa_df, ratio_criteria, top_n=10)
functions.plot_sector_treemap(top_N_data)
# Grouping the top 5 to see the distribution
summary = top_N_data.groupby(['Sector', 'Industry', 'Name', 'Ticker'])[['Score %']].max().sort_values('Score %', ascending=False)
display(summary.style.background_gradient(cmap='Greens'))
| Score % | ||||
|---|---|---|---|---|
| Sector | Industry | Name | Ticker | |
| Communication Services | Internet Content & Information | Alphabet Inc. | GOOG | 83.330000 |
| Technology | Semiconductors | Taiwan Semiconductor Manufacturing Company Limited | TSM | 83.330000 |
| NVIDIA Corporation | NVDA | 77.780000 | ||
| Communication Services | Internet Content & Information | Alphabet Inc. | GOOGL | 77.780000 |
| Industrials | Airports & Air Services | Grupo Aeroportuario del Centro Norte, S.A.B. de C.V. | OMAB | 77.780000 |
| Communication Services | Internet Content & Information | Meta Platforms, Inc. | META | 77.780000 |
| Entertainment | Netflix, Inc. | NFLX | 72.220000 | |
| Financial Services | Insurance - Diversified | Berkshire Hathaway Inc. | BRK-B | 72.220000 |
| Technology | Semiconductor Equipment & Materials | ASML Holding N.V. | ASML | 72.220000 |
| Software - Infrastructure | Microsoft Corporation | MSFT | 72.220000 |
# TOP N BY SECTOR AND INDUSTRY
functions.top_N_sector_industry(fa_df, ratio_criteria, n_per_sector=10)
# TOP 1 BY SECTOR
final_picks = functions.top_1_sector(fa_df, ratio_criteria)
print("\n--- FINAL BEST-IN-CLASS PORTFOLIO SELECTION ---")
display(final_picks.style.hide(axis="index").background_gradient(subset=['Score %'], cmap='RdYlGn'))
--- FINAL BEST-IN-CLASS PORTFOLIO SELECTION ---
| Sector | Industry | Ticker | Score % |
|---|---|---|---|
| Communication Services | Internet Content & Information | GOOG | 83.330000 |
| Technology | Semiconductors | TSM | 83.330000 |
| Industrials | Airports & Air Services | OMAB | 77.780000 |
| Financial Services | Insurance - Diversified | BRK-B | 72.220000 |
| Consumer Defensive | Beverages - Non-Alcoholic | KO | 66.670000 |
| Consumer Cyclical | Restaurants | MCD | 66.670000 |
| Energy | Oil & Gas Integrated | PBR | 61.110000 |
| Real Estate | REIT - Industrial | PSA | 55.560000 |
| Healthcare | Healthcare Plans | UNH | 38.890000 |
| ETF, others | ETF, others | VGT | 33.330000 |
# Portfolio Summary - Beta and Scores
# Option A: All Assets
stats_all = functions.get_portfolio_summary(fa_df, ratio_criteria, mode="all")
display(stats_all.style.hide(axis="index"))
# Option B: Top 10 Overall
stats_top10 = functions.get_portfolio_summary(fa_df, ratio_criteria, mode="top_n", top_n=10)
display(stats_top10.style.hide(axis="index"))
# Option C: Top 1 per Sector
stats_sector = functions.get_portfolio_summary(fa_df, ratio_criteria, mode="sector_best")
display(stats_sector.style.hide(axis="index"))
--- PORTFOLIO SUMMARY: ALL TICKERS --- Tickers Included (45): GOOG, TSM, META, OMAB, GOOGL, NVDA, BRK-B, ASML, MSFT, NFLX, ADBE, KO, MCD, AAPL, CAT, CME, PBR, MAR, PLTR, PSA, SPG, BAC, QSR, DIS, HD, NU, UNH, CRWD, LMT, VGT, SOXX, TSLA, VYM, SPYG, SOFI, SPYD, GLD, VOOV, QQQM, VOOG, VUG, VTV, VOO, QYLD, SERV
| Metric | Value |
|---|---|
| Selection Strategy | ALL TICKERS |
| Total Tickers | 45 |
| Average Match Score | 51.73% |
| Average Beta | 0.75 |
| Portfolio Character | CONSERVATIVE / DEFENSIVE |
--- PORTFOLIO SUMMARY: TOP 10 OVERALL --- Tickers Included (10): GOOG, TSM, META, OMAB, GOOGL, NVDA, BRK-B, ASML, MSFT, NFLX
| Metric | Value |
|---|---|
| Selection Strategy | TOP 10 OVERALL |
| Total Tickers | 10 |
| Average Match Score | 76.67% |
| Average Beta | 1.25 |
| Portfolio Character | AGGRESSIVE / HIGH VOLATILITY |
--- PORTFOLIO SUMMARY: TOP 1 PER SECTOR --- Tickers Included (10): GOOG, TSM, OMAB, BRK-B, KO, MCD, PBR, PSA, UNH, VGT
| Metric | Value |
|---|---|
| Selection Strategy | TOP 1 PER SECTOR |
| Total Tickers | 10 |
| Average Match Score | 63.89% |
| Average Beta | 0.60 |
| Portfolio Character | CONSERVATIVE / DEFENSIVE |
Fundamental Analysis ONE Ticker¶
TS302_Stock Full Analysis.ipynb
This section is to see in more detail any particular ticker
# 1. Create the widget
ticker_dropdown = widgets.Dropdown(
options=tickers,
#options=["KOF", "AAPL", "MSFT", "MA","NVDA", "GOOGL", "AMZN", "META", "TSM","BRK-B", "V", "JPM", "XOM", "LLY", "MRK", "UNH", "PG", "MA","CVX", "KO", "PEP", "COST", "TMO", "ORCL", "CSCO", "NKE", "VZ", "ASML", "TXN", "ABT", "TM", "SAP", "AMD", "NFLX", "NOW", "ADBE", "LVMUY", "BABA", "SHEL", "TMUS", "QCOM", "PFE", "SNY", "AZN", "TOT", "GSK", "RIO", "BHP", "MCD"],
#options=["BTC-USD", "ETH-USD", "USDT-USD", "XRP-USD", "LTC-USD", "ADA-USD", "DOT-USD", "BCH-USD", "XLM-USD", "LINK-USD"]
value=tickers[0], # Default selected value (must be from the options)
description='Select Ticker:',
disabled=False,
)
def on_change(selected_ticker):
clear_output(wait=False)
display(fa_df.loc[selected_ticker])
global ticker_widget
ticker_widget = selected_ticker
return ticker_widget
# # 4. Link the function to the widget and capture the output
interactive_plot = widgets.interactive_output(on_change, {'selected_ticker': ticker_dropdown})
# # 5. Display the widget and the output area in your notebook cell
display(ticker_dropdown, interactive_plot)
Dropdown(description='Select Ticker:', options=('QYLD', 'NVDA', 'PBR', 'PLTR', 'MSFT', 'AAPL', 'NU', 'DIS', 'V…
Output()
# Choose any ticket from the list above
ticker = yf.Ticker(ticker_widget)
ticker_name = ticker.info.get('symbol')
print(ticker_name)
QYLD
History
# Example of history() of any ONE ticker for "1y"
hist = ticker.history(period="1y", auto_adjust=True)
print(ticker_name)
display(hist)
QYLD
| Open | High | Low | Close | Volume | Dividends | Stock Splits | Capital Gains | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2025-01-06 00:00:00-05:00 | 16.399330 | 16.434827 | 16.372709 | 16.399330 | 4535000 | 0.0 | 0.0 | 0.0 |
| 2025-01-07 00:00:00-05:00 | 16.443700 | 16.443700 | 16.248469 | 16.283966 | 8486600 | 0.0 | 0.0 | 0.0 |
| 2025-01-08 00:00:00-05:00 | 16.297274 | 16.328334 | 16.195223 | 16.310585 | 11652700 | 0.0 | 0.0 | 0.0 |
| 2025-01-10 00:00:00-05:00 | 16.275095 | 16.283969 | 16.062117 | 16.159731 | 15684100 | 0.0 | 0.0 | 0.0 |
| 2025-01-13 00:00:00-05:00 | 16.017742 | 16.133106 | 15.955624 | 16.133106 | 8383300 | 0.0 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2025-12-29 00:00:00-05:00 | 17.719999 | 17.750000 | 17.709999 | 17.740000 | 4555200 | 0.0 | 0.0 | 0.0 |
| 2025-12-30 00:00:00-05:00 | 17.730000 | 17.760000 | 17.725000 | 17.730000 | 4812700 | 0.0 | 0.0 | 0.0 |
| 2025-12-31 00:00:00-05:00 | 17.730000 | 17.740000 | 17.670000 | 17.670000 | 4961300 | 0.0 | 0.0 | 0.0 |
| 2026-01-02 00:00:00-05:00 | 17.750000 | 17.780001 | 17.620001 | 17.680000 | 10828500 | 0.0 | 0.0 | 0.0 |
| 2026-01-05 00:00:00-05:00 | 17.725000 | 17.789000 | 17.719999 | 17.760000 | 5833600 | 0.0 | 0.0 | 0.0 |
250 rows × 8 columns
- Info
- Income Statement
- Balance Sheet
# info
ticker_info = ticker.info
# Optional: Print in JSON format all info
#print(json.dumps(ticker_info, indent=4))
#Income Statement
ticker_income_stmt = ticker.income_stmt
# Optional: Print Income Statement
#print(ticker_income_stmt)
#Balance Sheet
ticker_balance_sheet = ticker.balance_sheet
# Optional: Print Balance Sheet
#print(ticker_balance_sheet)
# To-Do: Support the Stock selection based on Ratios using the Financial Statements
# Print info by category (some selected data only)
def print_info_by_category(info_list, name):
print(f"\n{name}")
for key in info_list:
try:
value = ticker_info.get(key, 'N/A')
print(f"{key}: {value}")
except Exception as e:
print(f"Error retrieving '{key} for {ticker_name}: {e}")
# One can choose which info parameters to print in each category:
basic_info = ['symbol', 'longName', 'sector', 'industry', 'country']
market_info = ['currentPrice', 'marketCap', 'volume', '52WeekChange', 'fiftyTwoWeekHigh', 'fiftyTwoWeekLow']
financial_info = ['priceToBook', 'forwardPE', 'trailingPE', 'profitMargins', 'totalRevenue', 'debtToEquity',
'epsForward','ebitda','floatShares','forwardEps','grossMargins','grossProfits','operatingCashflow',
'operatingMargins','returnOnAssets','returnOnEquity','revenueGrowth','revenuePerShare','impliedSharesOutstanding',
'totalCash','totalDebt']
dividends_info = ['dividendYield','payoutRatio','dividendRate']
shares_info = ['heldPercentInsiders','heldPercentInstitutions']
technical_info = ['sharesOutstanding','beta','currency']
print_info_by_category(basic_info, "1. Basic info:")
print_info_by_category(market_info, "2. Market info:")
print_info_by_category(financial_info, "3. Financial info:")
print_info_by_category(dividends_info, "4. Dividends info:")
print_info_by_category(shares_info, "5. Shares management info:")
print_info_by_category(technical_info, "6. Technical info:")
# Market Cap = Current Share Price × Shares Outstanding
1. Basic info: symbol: QYLD longName: Global X NASDAQ 100 Covered Call ETF sector: N/A industry: N/A country: N/A 2. Market info: currentPrice: N/A marketCap: N/A volume: 5818312 52WeekChange: N/A fiftyTwoWeekHigh: 18.89 fiftyTwoWeekLow: 14.475 3. Financial info: priceToBook: N/A forwardPE: N/A trailingPE: 34.672573 profitMargins: N/A totalRevenue: N/A debtToEquity: N/A epsForward: N/A ebitda: N/A floatShares: N/A forwardEps: N/A grossMargins: N/A grossProfits: N/A operatingCashflow: N/A operatingMargins: N/A returnOnAssets: N/A returnOnEquity: N/A revenueGrowth: N/A revenuePerShare: N/A impliedSharesOutstanding: N/A totalCash: N/A totalDebt: N/A 4. Dividends info: dividendYield: 10.46 payoutRatio: N/A dividendRate: N/A 5. Shares management info: heldPercentInsiders: N/A heldPercentInstitutions: N/A 6. Technical info: sharesOutstanding: N/A beta: N/A currency: USD
TO-DO: Monitorear Recompra o dilucion de acciones. Con base en el numero de acciones disponibles por anio
Dividends, Splits and Recommendations
# 7. Other info:
print("\n7. Other info:")
other_info = {'Dividends' : ticker.dividends,
'Splits' : ticker.splits,
'Recommendations' : ticker.recommendations
# 'Recommendations Summary' : ticker.recommendations_summary
}
functions.print_dividends_splits_recommendations(other_info, ticker_name)
7. Other info:
HTTP Error 404:
Dividends:
Dividends:
Date
2014-01-22 00:00:00-05:00 0.257
2014-02-26 00:00:00-05:00 0.193
2014-03-26 00:00:00-04:00 0.193
2014-04-23 00:00:00-04:00 0.263
2014-05-21 00:00:00-04:00 0.245
...
2025-08-18 00:00:00-04:00 0.168
2025-09-22 00:00:00-04:00 0.170
2025-10-20 00:00:00-04:00 0.173
2025-11-24 00:00:00-05:00 0.173
2025-12-22 00:00:00-05:00 0.178
Name: Dividends, Length: 139, dtype: float64
There are not Splits in this period for 'QYLD' There are not Recommendations in this period for 'QYLD'
import operator
from dataclasses import dataclass
from typing import Callable, Any, Optional
RetrievalFunc = Callable[[dict], Any] #ticker.info : is a dictionary: ticker.info.get('trailingPE', 'N/A'). Look for ticker_info = ticker.info above
CompareFunc = Callable[[Any, Any], bool]
@dataclass
class FinancialRule:
retrieval_func: RetrievalFunc # function used to extract the specific metric from the data source
metric_name: str
target_value: Optional[Any] = None
comparison_func: Optional[Callable[[Any, Any], bool]] = None
# for printing pruposes
# target_value: float # The target value to compare against
# comparison_func: CompareFunc # comparison operator function (e.g., operator.lt for <)
# financial_rules = {
# 'D/E_%': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('debtToEquity', 'N/A'), target_value=100.0, comparison_func=operator.gt, metric_name='D/E_%')
# }
financial_rules = {
'Name': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('longName', 'N/A'), metric_name='Name'),
'Sector': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('sector', 'ETF, others'), metric_name='Sector'),
'Industry': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('industry', 'ETF, others'), metric_name='Industry'),
'CAGR_%': FinancialRule(retrieval_func=lambda ticker_info: functions.get_cagr(ticker_info.get('symbol'), start_date, today), target_value=10.0, comparison_func=operator.ge, metric_name='CAGR_%'),
'P/E Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('trailingPE', 'N/A'), target_value=25.0, comparison_func=operator.lt, metric_name='P/E Ratio'),
'P/B Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('priceToBook', 'N/A'), target_value=2.0, comparison_func=operator.lt, metric_name='P/B Ratio'),
'ROIC_%': FinancialRule(retrieval_func=lambda ticker_info: functions.get_roic(ticker_info.get('symbol')), target_value=15.0, comparison_func=operator.gt, metric_name='ROIC_%'),
'D/E_%': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('debtToEquity', 'N/A'), target_value=100.0, comparison_func=operator.lt, metric_name='D/E_%'),
'EPS_usd': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('epsForward', 'N/A'), target_value=0.0, comparison_func=operator.gt, metric_name='EPS_usd'),
'ROE Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('returnOnEquity', 'N/A'), target_value=0.15, comparison_func=operator.ge, metric_name='ROE Ratio'),
'EBIT Margin_%': FinancialRule(retrieval_func=lambda ticker_info: functions.get_ebit_margin(ticker_info.get('symbol')), target_value=10.0, comparison_func=operator.ge, metric_name='EBIT Margin_%'),
'Gross Margin_ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('grossMargins', 'N/A'), target_value=0.40, comparison_func=operator.ge, metric_name='Gross Margin_ratio'),
'Net Margin_%': FinancialRule(retrieval_func=lambda ticker_info: functions.get_net_margin(ticker_info.get('symbol')), target_value=15.0, comparison_func=operator.ge, metric_name='Net Margin_%'),
'Current Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('currentRatio', 'N/A'), target_value=1.5, comparison_func=operator.ge, metric_name='Current Ratio'),
'Overall Risk': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('overallRisk', 'N/A'), target_value=5.0, comparison_func=operator.lt, metric_name='Overall Risk'),
'Beta': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('beta', 'N/A'), target_value=1.0, comparison_func=operator.eq, metric_name='Beta'),
'EBITDA_usd': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('ebitda', 'N/A'), target_value=0.0, comparison_func=operator.gt, metric_name='EBITDA_usd'),
'EBITDA Margins Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('ebitdaMargins', 'N/A'), target_value=0.15, comparison_func=operator.gt, metric_name='EBITDA Margins Ratio'),
'Earning Growth Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('earningsGrowth', 'N/A'), target_value=0.15, comparison_func=operator.gt, metric_name='Earning Growth Ratio'),
'Revenue Growth Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('revenueGrowth', 'N/A'), target_value=0.15, comparison_func=operator.gt, metric_name='Revenue Growth Ratio'),
'Operating Margins Ratio': FinancialRule(retrieval_func=lambda ticker_info: ticker_info.get('operatingMargins', 'N/A'), target_value=0.15, comparison_func=operator.gt, metric_name='Operating Margins Ratio'),
}
def evaluate_ticker_rules(financial_rules: dict, ticker_info: dict):
print(f"---Evaluating Rules for {ticker_info.get('symbol', 'Unknown Ticker')}---")
if not ticker_info.get('symbol'):
print("Status: ⚠️ SKIP (No ticker data available)")
return
for rule_name, rule in financial_rules.items():
# 1. Call the stored retrieval function to get the actual metric value
actual_value = rule.retrieval_func(ticker_info)
print(f"\nRule: {rule_name}")
print(f"Metric: {rule.metric_name}, Value found: {actual_value}")
if rule.comparison_func is not None:
if actual_value is None or actual_value == 'N/A':
print("Status: ⚠️ SKIP (Data for comparison not available)")
continue
# 2. Call the stored comparison function
is_pass = rule.comparison_func(actual_value, rule.target_value)
if is_pass:
print(f"Status: ✅ PASS ({actual_value} is acceptable)")
else:
print(f"Status: ❌ FAIL ({actual_value} fails rule to be {rule.comparison_func.__name__} {rule.target_value})")
else:
print("Status: ✅ INFO (No comparison needed)")
evaluate_ticker_rules(financial_rules, ticker_info)
---Evaluating Rules for QYLD--- Rule: Name Metric: Name, Value found: Global X NASDAQ 100 Covered Call ETF Status: ✅ INFO (No comparison needed) Rule: Sector Metric: Sector, Value found: ETF, others Status: ✅ INFO (No comparison needed) Rule: Industry Metric: Industry, Value found: ETF, others Status: ✅ INFO (No comparison needed) Rule: CAGR_% Metric: CAGR_%, Value found: 7.28 Status: ❌ FAIL (7.28 fails rule to be ge 10.0) Rule: P/E Ratio Metric: P/E Ratio, Value found: 34.672573 Status: ❌ FAIL (34.672573 fails rule to be lt 25.0) Rule: P/B Ratio Metric: P/B Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Error (get_roic): Financial data for 'QYLD' not available or incomplete. Rule: ROIC_% Metric: ROIC_%, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: D/E_% Metric: D/E_%, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: EPS_usd Metric: EPS_usd, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: ROE Ratio Metric: ROE Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) (get_ebit_margin) No annual income statement found for 'QYLD'. Rule: EBIT Margin_% Metric: EBIT Margin_%, Value found: None Status: ⚠️ SKIP (Data for comparison not available) Rule: Gross Margin_ratio Metric: Gross Margin_ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Error (get_net_margin): Financial data for 'QYLD' not available. Rule: Net Margin_% Metric: Net Margin_%, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Current Ratio Metric: Current Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Overall Risk Metric: Overall Risk, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Beta Metric: Beta, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: EBITDA_usd Metric: EBITDA_usd, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: EBITDA Margins Ratio Metric: EBITDA Margins Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Earning Growth Ratio Metric: Earning Growth Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Revenue Growth Ratio Metric: Revenue Growth Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available) Rule: Operating Margins Ratio Metric: Operating Margins Ratio, Value found: N/A Status: ⚠️ SKIP (Data for comparison not available)
All Tickers in Portfolio¶
Import Prices from yfinance (All Tickers)¶
# import data from yahoo Finance
print(f"Number of days since Brokerage account was opened: {no_days}")
df = yf.download(tickers, start=start_date, end=today, auto_adjust=True)
df.info()
[*** 7% ] 3 of 45 completed
Number of days since Brokerage account was opened: 1717 days, 0:00:00
[*********************100%***********************] 45 of 45 completed
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1180 entries, 2021-04-26 to 2026-01-05
Columns: 225 entries, ('Close', 'AAPL') to ('Volume', 'VYM')
dtypes: float64(182), int64(43)
memory usage: 2.0 MB
Prices (Close)¶
prices_raw = df["Close"]
print('-- Last 5 days of prices --')
display(prices_raw.tail(5))
-- Last 5 days of prices --
| Ticker | AAPL | ADBE | ASML | BAC | BRK-B | CAT | CME | CRWD | DIS | GLD | ... | TSLA | TSM | UNH | VGT | VOO | VOOG | VOOV | VTV | VUG | VYM |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||||||||||||||
| 2025-12-29 | 273.760010 | 353.160004 | 1066.000000 | 55.349998 | 501.049988 | 578.609985 | 278.420013 | 475.910004 | 114.190002 | 398.600006 | ... | 459.640015 | 300.920013 | 328.940002 | 763.099976 | 632.599976 | 448.600006 | 206.630005 | 192.589996 | 492.540009 | 144.699997 |
| 2025-12-30 | 273.079987 | 352.510010 | 1072.140015 | 55.279999 | 503.709991 | 577.390015 | 275.829987 | 475.630005 | 114.790001 | 398.890015 | ... | 454.429993 | 299.579987 | 332.160004 | 760.890015 | 631.719971 | 447.769989 | 206.410004 | 192.369995 | 491.690002 | 144.550003 |
| 2025-12-31 | 271.859985 | 349.989990 | 1069.859985 | 55.000000 | 502.649994 | 572.869995 | 273.079987 | 468.760010 | 113.769997 | 396.309998 | ... | 449.720001 | 303.890015 | 330.109985 | 753.780029 | 627.130005 | 444.589996 | 204.850006 | 190.990005 | 487.859985 | 143.520004 |
| 2026-01-02 | 271.010010 | 333.299988 | 1163.780029 | 55.950001 | 496.850006 | 598.409973 | 269.679993 | 453.579987 | 111.849998 | 398.279999 | ... | 438.070007 | 319.609985 | 336.399994 | 755.979980 | 628.299988 | 444.850006 | 205.610001 | 192.809998 | 486.200012 | 144.759995 |
| 2026-01-05 | 267.260010 | 331.559998 | 1228.189941 | 56.889999 | 498.519989 | 616.099976 | 275.059998 | 456.549988 | 114.070000 | 408.760010 | ... | 451.670013 | 322.250000 | 342.019989 | 757.419983 | 632.460022 | 446.510010 | 207.509995 | 194.649994 | 488.450012 | 145.820007 |
5 rows × 45 columns
# drop NaN (days without price)
prices = prices_raw.dropna()
display(prices.tail(5))
| Ticker | AAPL | ADBE | ASML | BAC | BRK-B | CAT | CME | CRWD | DIS | GLD | ... | TSLA | TSM | UNH | VGT | VOO | VOOG | VOOV | VTV | VUG | VYM |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||||||||||||||
| 2025-12-29 | 273.760010 | 353.160004 | 1066.000000 | 55.349998 | 501.049988 | 578.609985 | 278.420013 | 475.910004 | 114.190002 | 398.600006 | ... | 459.640015 | 300.920013 | 328.940002 | 763.099976 | 632.599976 | 448.600006 | 206.630005 | 192.589996 | 492.540009 | 144.699997 |
| 2025-12-30 | 273.079987 | 352.510010 | 1072.140015 | 55.279999 | 503.709991 | 577.390015 | 275.829987 | 475.630005 | 114.790001 | 398.890015 | ... | 454.429993 | 299.579987 | 332.160004 | 760.890015 | 631.719971 | 447.769989 | 206.410004 | 192.369995 | 491.690002 | 144.550003 |
| 2025-12-31 | 271.859985 | 349.989990 | 1069.859985 | 55.000000 | 502.649994 | 572.869995 | 273.079987 | 468.760010 | 113.769997 | 396.309998 | ... | 449.720001 | 303.890015 | 330.109985 | 753.780029 | 627.130005 | 444.589996 | 204.850006 | 190.990005 | 487.859985 | 143.520004 |
| 2026-01-02 | 271.010010 | 333.299988 | 1163.780029 | 55.950001 | 496.850006 | 598.409973 | 269.679993 | 453.579987 | 111.849998 | 398.279999 | ... | 438.070007 | 319.609985 | 336.399994 | 755.979980 | 628.299988 | 444.850006 | 205.610001 | 192.809998 | 486.200012 | 144.759995 |
| 2026-01-05 | 267.260010 | 331.559998 | 1228.189941 | 56.889999 | 498.519989 | 616.099976 | 275.059998 | 456.549988 | 114.070000 | 408.760010 | ... | 451.670013 | 322.250000 | 342.019989 | 757.419983 | 632.460022 | 446.510010 | 207.509995 | 194.649994 | 488.450012 | 145.820007 |
5 rows × 45 columns
print(f"prices_raw shape: {prices_raw.shape}")
print(f"prices shape: {prices.shape}")
if len(prices) < len(prices_raw):
print(f"Reduction of {(len(prices_raw) - len(prices))}")
print(f"It can be noticed that the amount of rows in the dataframe dropped from {len(prices_raw)} to {len(prices)} after the drop.na() operation. This is because there are a couple of assets that are relatively new in the public market (post IPO).")
prices_raw shape: (1180, 45) prices shape: (458, 45) Reduction of 722 It can be noticed that the amount of rows in the dataframe dropped from 1180 to 458 after the drop.na() operation. This is because there are a couple of assets that are relatively new in the public market (post IPO).
print(f"Reminder: the original 'start_date' was: {start_date.date().strftime("%B %d, %Y")}, when the original portfolio was created.")
df_ = df['Close']
first_valid_dates = df_.apply(pd.Series.first_valid_index)
first_valid_dates.name = "First Valid Date"
first_valid_dates = first_valid_dates.sort_values(ascending=False)
print(f"\nThe asset with the smallest amount of data available is: '{first_valid_dates.index[0]}' starting from '{first_valid_dates.iloc[0].date().strftime("%B %d, %Y")}' only.")
print("\nOldest available dates for each asset:")
display(first_valid_dates)
Reminder: the original 'start_date' was: April 25, 2021, when the original portfolio was created. The asset with the smallest amount of data available is: 'SERV' starting from 'March 08, 2024' only. Oldest available dates for each asset:
Ticker SERV 2024-03-08 NU 2021-12-09 AAPL 2021-04-26 SPYD 2021-04-26 PSA 2021-04-26 QQQM 2021-04-26 QSR 2021-04-26 QYLD 2021-04-26 SOFI 2021-04-26 SOXX 2021-04-26 SPG 2021-04-26 SPYG 2021-04-26 PBR 2021-04-26 TSLA 2021-04-26 TSM 2021-04-26 UNH 2021-04-26 VGT 2021-04-26 VOO 2021-04-26 VOOG 2021-04-26 VOOV 2021-04-26 VTV 2021-04-26 VUG 2021-04-26 PLTR 2021-04-26 OMAB 2021-04-26 ADBE 2021-04-26 GOOG 2021-04-26 ASML 2021-04-26 BAC 2021-04-26 BRK-B 2021-04-26 CAT 2021-04-26 CME 2021-04-26 CRWD 2021-04-26 DIS 2021-04-26 GLD 2021-04-26 GOOGL 2021-04-26 NVDA 2021-04-26 HD 2021-04-26 KO 2021-04-26 LMT 2021-04-26 MAR 2021-04-26 MCD 2021-04-26 META 2021-04-26 MSFT 2021-04-26 NFLX 2021-04-26 VYM 2021-04-26 Name: First Valid Date, dtype: datetime64[ns]
# -- PLOT ASSET PRICES --
functions.plot_prices(prices=prices, yaxis_label="Price (USD)")
Stats (describe)¶
# statistics about prices
prices.describe()
| Ticker | AAPL | ADBE | ASML | BAC | BRK-B | CAT | CME | CRWD | DIS | GLD | ... | TSLA | TSM | UNH | VGT | VOO | VOOG | VOOV | VTV | VUG | VYM |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | ... | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 | 458.000000 |
| mean | 222.552679 | 437.168252 | 835.958809 | 43.304069 | 467.779913 | 389.081520 | 236.470775 | 387.847784 | 105.330561 | 277.980262 | ... | 303.332074 | 201.119318 | 440.994565 | 617.778433 | 536.028584 | 365.229192 | 184.176589 | 169.630523 | 406.587243 | 126.395603 |
| std | 28.227665 | 76.543920 | 137.833934 | 5.724365 | 36.503627 | 78.722380 | 32.720417 | 82.221034 | 10.323902 | 56.767730 | ... | 94.636566 | 49.471145 | 104.222733 | 81.988133 | 50.927276 | 46.526895 | 10.670231 | 11.152038 | 49.141947 | 9.622135 |
| min | 163.664917 | 312.399994 | 590.981628 | 33.286674 | 396.730011 | 270.854095 | 183.360397 | 217.889999 | 80.826134 | 199.710007 | ... | 142.050003 | 124.747917 | 234.701340 | 468.845581 | 445.162018 | 283.491333 | 160.399734 | 148.670639 | 319.818878 | 109.263657 |
| 25% | 204.992336 | 361.214996 | 720.596664 | 38.356688 | 445.197495 | 337.269882 | 203.922478 | 320.304993 | 96.814312 | 230.795002 | ... | 221.462502 | 165.846745 | 331.912491 | 556.428314 | 496.731300 | 331.431778 | 175.030312 | 161.032127 | 370.120850 | 118.651709 |
| 50% | 223.477219 | 438.600006 | 797.170013 | 43.228786 | 472.275009 | 358.955170 | 237.254982 | 377.800003 | 109.514038 | 263.349991 | ... | 302.714996 | 190.106636 | 473.674393 | 605.396118 | 531.847992 | 360.575485 | 183.908020 | 169.886086 | 403.221786 | 126.483810 |
| 75% | 238.251236 | 502.427505 | 954.881851 | 46.758083 | 496.355011 | 411.348167 | 268.550484 | 461.902496 | 112.741642 | 309.809990 | ... | 389.190002 | 231.559891 | 530.617584 | 682.324539 | 574.526367 | 405.175117 | 191.088543 | 176.452358 | 448.428337 | 133.308155 |
| max | 286.190002 | 586.549988 | 1228.189941 | 56.889999 | 539.799988 | 625.609985 | 286.685730 | 557.530029 | 123.176605 | 416.739990 | ... | 489.880005 | 322.250000 | 607.890625 | 800.707397 | 634.840027 | 454.862305 | 207.509995 | 194.649994 | 503.744995 | 146.816788 |
8 rows × 45 columns
Normalized Prices¶
Normalize to 100
Normalizar precios de diferentes magnitudes dividiendo entre el primer registro. Todos los precios parten del mismo punto que es el 100.
$$ \frac {P_t}{P_0} * 100 $$
Nota: esto ya no es mas el Precio al Cierre sino un indice de crecimiento en el tiempo.
# Normalized Prices
prices_normalized = (prices / prices.iloc[0]) * 100
# -- PLOT ASSET PRICES --
functions.plot_prices(prices=prices_normalized, yaxis_label="Price (USD)")
Daily Returns¶
TS303_yfinance Indices Bursatiles.ipynb
Normal (simple or arithmetic) returns:
$$Return(R_t) = \frac{P_t - P_{t-1}}{P_{t-1}} = \frac {P_t}{P_{t-1}} -1 $$
# Daily Returns. Using pct_change()
# Normal (simple or arithmetic) returns
# Expresed in FRACTION.
# If Percentage is needed then multiply by 100.
daily_returns = prices.pct_change(fill_method=None)
daily_returns = daily_returns.dropna()
daily_returns.tail(5)
| Ticker | AAPL | ADBE | ASML | BAC | BRK-B | CAT | CME | CRWD | DIS | GLD | ... | TSLA | TSM | UNH | VGT | VOO | VOOG | VOOV | VTV | VUG | VYM |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||||||||||||||
| 2025-12-29 | 0.001317 | -0.001809 | -0.006292 | -0.014599 | 0.005519 | -0.007530 | 0.006107 | -0.010973 | 0.005548 | -0.043528 | ... | -0.032724 | -0.006340 | -0.008709 | -0.005305 | -0.003529 | -0.004637 | -0.001401 | -0.000986 | -0.005090 | -0.002688 |
| 2025-12-30 | -0.002484 | -0.001841 | 0.005760 | -0.001265 | 0.005309 | -0.002108 | -0.009303 | -0.000588 | 0.005254 | 0.000728 | ... | -0.011335 | -0.004453 | 0.009789 | -0.002896 | -0.001391 | -0.001850 | -0.001065 | -0.001142 | -0.001726 | -0.001037 |
| 2025-12-31 | -0.004468 | -0.007149 | -0.002127 | -0.005065 | -0.002104 | -0.007828 | -0.009970 | -0.014444 | -0.008886 | -0.006468 | ... | -0.010365 | 0.014387 | -0.006172 | -0.009344 | -0.007266 | -0.007102 | -0.007558 | -0.007174 | -0.007789 | -0.007126 |
| 2026-01-02 | -0.003127 | -0.047687 | 0.087787 | 0.017273 | -0.011539 | 0.044583 | -0.012451 | -0.032383 | -0.016876 | 0.004971 | ... | -0.025905 | 0.051729 | 0.019054 | 0.002919 | 0.001866 | 0.000585 | 0.003710 | 0.009529 | -0.003403 | 0.008640 |
| 2026-01-05 | -0.013837 | -0.005220 | 0.055345 | 0.016801 | 0.003361 | 0.029562 | 0.019950 | 0.006548 | 0.019848 | 0.026313 | ... | 0.031045 | 0.008260 | 0.016706 | 0.001905 | 0.006621 | 0.003732 | 0.009241 | 0.009543 | 0.004628 | 0.007323 |
5 rows × 45 columns
results_df = functions.plot_returns_distributions(daily_returns, num_cols=4)
================================================================================ FAT-TAIL RISK RANKING (Sorted by Highest Kurtosis) ================================================================================
| Mean | Median | Std Dev | Skewness | Kurtosis | |
|---|---|---|---|---|---|
| Ticker | |||||
| SERV | 0.0061 | -0.0034 | 0.1318 | 5.9456 | 90.6288 |
| QYLD | 0.0005 | 0.0006 | 0.0094 | 2.0870 | 42.2459 |
| UNH | -0.0003 | 0.0006 | 0.0252 | -2.2992 | 20.7657 |
| VOOV | 0.0005 | 0.0006 | 0.0086 | 0.4293 | 18.1899 |
| VOO | 0.0008 | 0.0011 | 0.0102 | 0.6920 | 17.8392 |
| ADBE | -0.0009 | -0.0001 | 0.0214 | -1.1348 | 15.0829 |
| QQQM | 0.0009 | 0.0016 | 0.0133 | 0.7721 | 14.1523 |
| VTV | 0.0006 | 0.0005 | 0.0084 | -0.0227 | 14.0826 |
| VUG | 0.0009 | 0.0014 | 0.0133 | 0.7047 | 14.0708 |
| VYM | 0.0006 | 0.0007 | 0.0086 | 0.0657 | 13.7854 |
| AAPL | 0.0012 | 0.0016 | 0.0180 | 0.9798 | 13.5882 |
| DIS | 0.0003 | -0.0001 | 0.0171 | 0.2075 | 13.1759 |
| SPYG | 0.0010 | 0.0017 | 0.0132 | 0.6352 | 13.0041 |
| VOOG | 0.0010 | 0.0016 | 0.0132 | 0.5869 | 12.9678 |
| LMT | 0.0006 | 0.0009 | 0.0144 | -1.7608 | 12.1996 |
| VGT | 0.0010 | 0.0022 | 0.0160 | 0.5416 | 11.9247 |
| SOXX | 0.0010 | 0.0012 | 0.0238 | 0.4233 | 9.1252 |
| BAC | 0.0012 | 0.0014 | 0.0159 | -0.6499 | 8.7776 |
| SPG | 0.0007 | 0.0014 | 0.0149 | -0.5224 | 8.4795 |
| META | 0.0008 | 0.0009 | 0.0221 | 0.2239 | 8.3067 |
| MSFT | 0.0005 | 0.0011 | 0.0142 | 0.5917 | 8.2515 |
| BRK-B | 0.0005 | 0.0008 | 0.0107 | -0.2961 | 7.6443 |
| PLTR | 0.0049 | 0.0030 | 0.0398 | 0.8398 | 6.5827 |
| NU | 0.0014 | 0.0019 | 0.0270 | -0.7662 | 6.5808 |
| ASML | 0.0009 | 0.0021 | 0.0273 | -0.3530 | 6.3391 |
| CAT | 0.0015 | 0.0009 | 0.0188 | 0.5463 | 6.2754 |
| NFLX | 0.0011 | 0.0015 | 0.0199 | -0.1149 | 6.2151 |
| SPYD | 0.0004 | 0.0007 | 0.0090 | -0.2973 | 5.6996 |
| MAR | 0.0006 | 0.0006 | 0.0163 | 0.3715 | 5.2052 |
| CRWD | 0.0012 | 0.0013 | 0.0288 | 0.2218 | 4.8889 |
| NVDA | 0.0022 | 0.0027 | 0.0319 | -0.0131 | 4.5842 |
| GOOG | 0.0020 | 0.0029 | 0.0189 | 0.4433 | 4.5308 |
| GOOGL | 0.0021 | 0.0029 | 0.0191 | 0.4620 | 4.4202 |
| TSLA | 0.0029 | 0.0010 | 0.0408 | 0.6801 | 4.3883 |
| PBR | 0.0002 | 0.0007 | 0.0184 | -0.2319 | 3.8937 |
| TSM | 0.0021 | 0.0027 | 0.0257 | -0.0901 | 3.0021 |
| GLD | 0.0016 | 0.0020 | 0.0115 | -0.5681 | 2.9093 |
| MCD | 0.0002 | 0.0001 | 0.0116 | -0.1298 | 2.8369 |
| OMAB | 0.0015 | 0.0011 | 0.0217 | -0.0996 | 2.6707 |
| KO | 0.0005 | 0.0003 | 0.0099 | 0.2686 | 2.4035 |
| SOFI | 0.0036 | 0.0047 | 0.0364 | -0.0165 | 2.3834 |
| PSA | 0.0000 | 0.0004 | 0.0143 | -0.2022 | 1.8007 |
| CME | 0.0008 | 0.0014 | 0.0110 | -0.6110 | 1.7262 |
| QSR | -0.0001 | -0.0004 | 0.0143 | -0.2684 | 1.4833 |
| HD | 0.0000 | -0.0010 | 0.0140 | 0.0574 | 1.4739 |
# -- PLOT DAILY RETURNS --
functions.plot_daily_returns(daily_returns)
Summary of Daily Returns¶
# stats and summary of daily retutns
print(f"Number of days of evaluation: {(today - daily_returns.index[0]).days}")
print(f"since {daily_returns.index[0].date().strftime("%B %d, %Y")} until {today.date().strftime("%B %d, %Y")}")
max_daily_return = daily_returns.describe().loc['max'].sort_values(ascending=False)
print(f"\nThe asset with the biggest Return in a single day is '{max_daily_return.index[0]}' with {100*max_daily_return.iloc[0]:,.5}%")
min_daily_return = daily_returns.describe().loc['min'].sort_values(ascending=True)
print(f"The asset with the lowest Return in a single day is '{min_daily_return.index[0]}' with {100*min_daily_return.iloc[0]:,.5}%")
average_daily_returns = daily_returns.mean()
print(f"The average of all Daily Returns is {average_daily_returns.mean()*100:,.4}%")
max_std = daily_returns.describe().loc['std'].sort_values(ascending=False)
print(f"\nThe asset with the biggest StdDev in Daily Returns is '{max_std.index[0]}' with {100*max_std.iloc[0]:,.5}%")
print(f"The asset with the smallest StdDev in Daily Returns is '{max_std.index[-1]}' with {100*max_std.iloc[-1]:,.5}%")
Number of days of evaluation: 666 since March 11, 2024 until January 06, 2026 The asset with the biggest Return in a single day is 'SERV' with 187.07% The asset with the lowest Return in a single day is 'SERV' with -77.647% The average of all Daily Returns is 0.1137% The asset with the biggest StdDev in Daily Returns is 'SERV' with 13.175% The asset with the smallest StdDev in Daily Returns is 'VTV' with 0.83946%
Annualized Returns¶
# Annualized Returns
annualized_return_tickers = daily_returns.mean() * 250
annualized_return_tickers.name = "Annualized Returns:"
annualized_return_tickers = annualized_return_tickers.sort_values(ascending=False)
print("Annualized Returns (%):")
print(f"{round(annualized_return_tickers, 2)* 100}")
Annualized Returns (%): Ticker SERV 153.0 PLTR 123.0 SOFI 90.0 TSLA 72.0 NVDA 55.0 TSM 53.0 GOOGL 51.0 GOOG 51.0 GLD 40.0 CAT 39.0 OMAB 38.0 NU 36.0 BAC 31.0 CRWD 29.0 AAPL 29.0 NFLX 28.0 SOXX 26.0 SPYG 25.0 VOOG 25.0 VGT 24.0 VUG 23.0 ASML 22.0 QQQM 22.0 META 21.0 VOO 19.0 CME 19.0 SPG 18.0 MAR 16.0 VYM 16.0 VTV 15.0 LMT 14.0 BRK-B 13.0 QYLD 13.0 VOOV 12.0 KO 12.0 MSFT 12.0 SPYD 11.0 DIS 7.0 PBR 6.0 MCD 5.0 PSA 1.0 HD 0.0 QSR -3.0 UNH -8.0 ADBE -22.0 Name: Annualized Returns:, dtype: float64 Ticker SERV 153.0 PLTR 123.0 SOFI 90.0 TSLA 72.0 NVDA 55.0 TSM 53.0 GOOGL 51.0 GOOG 51.0 GLD 40.0 CAT 39.0 OMAB 38.0 NU 36.0 BAC 31.0 CRWD 29.0 AAPL 29.0 NFLX 28.0 SOXX 26.0 SPYG 25.0 VOOG 25.0 VGT 24.0 VUG 23.0 ASML 22.0 QQQM 22.0 META 21.0 VOO 19.0 CME 19.0 SPG 18.0 MAR 16.0 VYM 16.0 VTV 15.0 LMT 14.0 BRK-B 13.0 QYLD 13.0 VOOV 12.0 KO 12.0 MSFT 12.0 SPYD 11.0 DIS 7.0 PBR 6.0 MCD 5.0 PSA 1.0 HD 0.0 QSR -3.0 UNH -8.0 ADBE -22.0 Name: Annualized Returns:, dtype: float64
Cumulative Returns in Period¶
# cumprod(): calculates the total return over a period by compounding the daily returns. (a)(ab)(abc)
# It reflects how an initial investment would grow if it were continuously reinvested and earned the daily returns.
cumulative_returns = (1 + daily_returns).cumprod()
cumulative_returns = (cumulative_returns - 1)*100
print("Cumulative Returns in %:")
cumulative_returns.tail(5)
Cumulative Returns in %:
| Ticker | AAPL | ADBE | ASML | BAC | BRK-B | CAT | CME | CRWD | DIS | GLD | ... | TSLA | TSM | UNH | VGT | VOO | VOOG | VOOV | VTV | VUG | VYM |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||||||||||||||
| 2025-12-29 | 61.654779 | -35.985789 | 8.910560 | 61.985575 | 24.283764 | 75.127795 | 38.696582 | 47.409012 | 5.601360 | 97.688834 | ... | 162.142138 | 111.308356 | -28.452585 | 47.946322 | 37.928557 | 51.768926 | 22.272159 | 27.397622 | 46.504114 | 30.200598 |
| 2025-12-30 | 61.253226 | -36.103607 | 9.537870 | 61.780716 | 24.943569 | 74.758547 | 37.406345 | 47.322285 | 6.156230 | 97.832666 | ... | 159.170755 | 110.367379 | -27.752206 | 47.517865 | 37.736686 | 51.488117 | 22.141975 | 27.252092 | 46.251283 | 30.065634 |
| 2025-12-31 | 60.532818 | -36.560389 | 9.304925 | 60.961281 | 24.680640 | 73.390473 | 36.036416 | 45.194363 | 5.212944 | 96.553086 | ... | 156.484550 | 113.393914 | -28.198104 | 46.139413 | 36.735916 | 50.412272 | 21.218855 | 26.339234 | 45.112059 | 29.138844 |
| 2026-01-02 | 60.030909 | -39.585639 | 18.900502 | 63.741523 | 23.241973 | 81.120654 | 34.342689 | 40.492482 | 3.437356 | 97.530124 | ... | 149.840320 | 124.432598 | -26.829970 | 46.565930 | 36.991012 | 50.500238 | 21.668577 | 27.543152 | 44.618307 | 30.254584 |
| 2026-01-05 | 57.816541 | -39.901032 | 25.481102 | 66.492494 | 23.656207 | 86.474885 | 37.022770 | 41.412414 | 5.490383 | 102.727769 | ... | 157.596682 | 126.286437 | -25.607571 | 46.845111 | 37.898043 | 51.061845 | 22.792888 | 28.760304 | 45.287560 | 31.208380 |
5 rows × 45 columns
# -- PLOT CUMULATIVE RETURNS --
functions.plot_cumulative_returns(cumulative_returns)
Final Cumulated Returns in Period¶
# Rendimientos Acumulados al Final del Tiempo en (%) use .prod() y -1
print(f"Final Cumulative Returns in (%) in {(today - daily_returns.index[0]).days} days of evaluation: ")
print(f"since {daily_returns.index[0].date().strftime("%B %d, %Y")} until {today.date().strftime("%B %d, %Y")}")
cumulative_returns_final = cumulative_returns.iloc[-1]
cumulative_returns_final.name = "Final Cumulative Returns (%)"
cumulative_returns_final = round(cumulative_returns_final.sort_values(ascending=False) , 1)
print("Final Cumulative Returns in %:")
display(cumulative_returns_final.all)
Final Cumulative Returns in (%) in 666 days of evaluation: since March 11, 2024 until January 06, 2026 Final Cumulative Returns in %:
<bound method Series.all of Ticker PLTR 568.4 SOFI 279.8 TSLA 157.6 GOOGL 135.5 GOOG 134.6 TSM 126.3 NVDA 115.0 GLD 102.7 CAT 86.5 OMAB 79.5 BAC 66.5 NU 62.1 AAPL 57.8 NFLX 51.2 SPYG 51.1 VOOG 51.1 VGT 46.8 VUG 45.3 QQQM 42.5 CRWD 41.4 SOXX 41.3 VOO 37.9 CME 37.0 SPG 32.3 VYM 31.2 META 31.0 VTV 28.8 MAR 26.5 ASML 25.5 QYLD 24.3 LMT 23.9 BRK-B 23.7 VOOV 22.8 KO 21.1 SPYD 19.6 MSFT 17.9 MCD 6.8 DIS 5.5 PBR 3.5 PSA -3.5 HD -3.8 QSR -10.2 UNH -25.6 ADBE -39.9 SERV -46.7 Name: Final Cumulative Returns (%), dtype: float64>
Summary Final Cumulated Returns¶
print(f"Number of days of evaluation (period): {(today - cumulative_returns.index[0]).days}")
print(f"From {cumulative_returns.index[0].date().strftime("%B %d, %Y")} until {today.date().strftime("%B %d, %Y")}")
print(f"The asset with the best cumulated return in the Period '{cumulative_returns_final.index[0]}' with {cumulative_returns_final.iloc[0]:,.5}% in the period")
print(f"The asset with the worst cumulative return in the Period '{cumulative_returns_final.index[-1]}' with {cumulative_returns_final.iloc[-1]:,.5}% in the period")
Number of days of evaluation (period): 666 From March 11, 2024 until January 06, 2026 The asset with the best cumulated return in the Period 'PLTR' with 568.4% in the period The asset with the worst cumulative return in the Period 'SERV' with -46.7% in the period
Risk, Annualized Volatility¶
Asset Volatility (risk or std)
$$ \sigma = risk = \sqrt{\frac {\sum(r - \bar{r})^2}{n-1}} $$
# Yearly volatility (risk)
# StdDev of daily returns in a 252-days year
annualized_volatility = daily_returns.std() * np.sqrt(252)
# Percentage (%)
annualized_volatility_percent = annualized_volatility * 100
# DataFrame of Annualized Volatility
volatility_df = pd.DataFrame(annualized_volatility_percent, columns=["Volatility (%)"])
print("Annualized Assets Volatility:")
volatility_df = round(volatility_df.sort_values(by="Volatility (%)", ascending=False), 2)
display(volatility_df)
Annualized Assets Volatility:
| Volatility (%) | |
|---|---|
| Ticker | |
| SERV | 209.15 |
| TSLA | 64.71 |
| PLTR | 63.15 |
| SOFI | 57.83 |
| NVDA | 50.56 |
| CRWD | 45.66 |
| ASML | 43.30 |
| NU | 42.78 |
| TSM | 40.79 |
| UNH | 39.95 |
| SOXX | 37.78 |
| META | 35.01 |
| OMAB | 34.40 |
| ADBE | 34.03 |
| NFLX | 31.53 |
| GOOGL | 30.36 |
| GOOG | 29.97 |
| CAT | 29.92 |
| PBR | 29.26 |
| AAPL | 28.62 |
| DIS | 27.16 |
| MAR | 25.89 |
| VGT | 25.43 |
| BAC | 25.18 |
| SPG | 23.62 |
| LMT | 22.85 |
| PSA | 22.66 |
| QSR | 22.63 |
| MSFT | 22.50 |
| HD | 22.24 |
| QQQM | 21.11 |
| VUG | 21.04 |
| SPYG | 20.98 |
| VOOG | 20.90 |
| MCD | 18.39 |
| GLD | 18.22 |
| CME | 17.54 |
| BRK-B | 17.04 |
| VOO | 16.15 |
| KO | 15.68 |
| QYLD | 14.87 |
| SPYD | 14.34 |
| VYM | 13.64 |
| VOOV | 13.62 |
| VTV | 13.33 |
Stats of Volatility
display(volatility_df.describe()) # all stocks
| Volatility (%) | |
|---|---|
| count | 45.000000 |
| mean | 32.794889 |
| std | 29.833583 |
| min | 13.330000 |
| 25% | 20.900000 |
| 50% | 25.430000 |
| 75% | 35.010000 |
| max | 209.150000 |
Summary Risk, Volatility¶
# summary and stats
print(f"Number of days of evaluation: {(today - cumulative_returns.index[0]).days}")
print(f"From {cumulative_returns.index[0].date().strftime("%B %d, %Y")} until {today.date().strftime("%B %d, %Y")}")
print(f"The asset with the biggest Annualized Volatility is '{volatility_df.index[0]}' with a {volatility_df.iloc[0].values[0]:,.5} %")
print(f"The asset with the lowest Annualized Volatility is '{volatility_df.index[-1]}' with a {volatility_df.iloc[-1].values[0]:,.5} %")
print(f"The Average Annualized Volatility of all assets is: {volatility_df.mean().values[0]:,.5} %")
Number of days of evaluation: 666 From March 11, 2024 until January 06, 2026 The asset with the biggest Annualized Volatility is 'SERV' with a 209.15 % The asset with the lowest Annualized Volatility is 'VTV' with a 13.33 % The Average Annualized Volatility of all assets is: 32.795 %
Dividend Yields (%)¶
# ANNUAL DIVIDEND YIELDS
yields = functions.plot_annual_dividnd_yields(daily_returns)
Annual Dividend Yields from yf 'dividendYield' (%):
AAPL 0.39 ADBE 0.00 ASML 0.60 BAC 1.97 BRK-B 0.00 CAT 0.98 CME 1.82 CRWD 0.00 DIS 1.31 GLD 0.00 GOOG 0.26 GOOGL 0.27 HD 2.67 KO 3.00 LMT 2.70 MAR 0.86 MCD 2.48 META 0.32 MSFT 0.77 NFLX 0.00 NU 0.00 NVDA 0.02 OMAB 4.48 PBR 14.31 PLTR 0.00 PSA 4.60 QQQM 0.49 QSR 3.72 QYLD 10.46 SERV 0.00 SOFI 0.00 SOXX 0.55 SPG 4.81 SPYD 4.46 SPYG 0.53 TSLA 0.00 TSM 1.04 UNH 2.58 VGT 0.41 VOO 1.12 VOOG 0.48 VOOV 1.80 VTV 2.05 VUG 0.42 VYM 2.42 Name: dividendYield, dtype: float64
# call the original portfolio [Ticker, Current QTY]
file_df
# Close prices df
close_prices = prices.iloc[-1]
close_prices.name = "Close Price"
# Merge original df with Close Prices
weights_df = pd.merge(file_df, close_prices, on='Ticker', how='inner')
# Add column of Amount Invested for each asset
weights_df['Investment'] = weights_df["Current QTY"] * weights_df["Close Price"]
# Calculate the Total Invested
Total_invested = weights_df['Investment'].sum()
print(f"Total Invested: ${Total_invested:,.2f}")
# Add column of weights of each asset
weights_df['Weights'] = weights_df['Investment'] / Total_invested
print(f"The sum of the weights is: {weights_df['Weights'].sum()}")
weights_df.set_index('Ticker', inplace=True)
weights_df.sort_values(by='Investment', ascending=False, inplace=True)
print("Sorted by Invested amount ($):")
display(round(weights_df, 2))
Total Invested: $68,831.74 The sum of the weights is: 1.0 Sorted by Invested amount ($):
| Current QTY | Close Price | Investment | Weights | |
|---|---|---|---|---|
| Ticker | ||||
| NVDA | 100.53 | 188.12 | 18911.48 | 0.27 |
| MSFT | 19.75 | 472.85 | 9339.75 | 0.14 |
| AAPL | 17.04 | 267.26 | 4554.12 | 0.07 |
| PLTR | 21.02 | 174.04 | 3658.95 | 0.05 |
| TSM | 10.03 | 322.25 | 3233.66 | 0.05 |
| VOOG | 6.30 | 446.51 | 2811.39 | 0.04 |
| GLD | 6.45 | 408.76 | 2636.24 | 0.04 |
| TSLA | 4.92 | 451.67 | 2224.30 | 0.03 |
| VOOV | 10.36 | 207.51 | 2150.75 | 0.03 |
| QYLD | 100.86 | 17.76 | 1791.28 | 0.03 |
| UNH | 5.04 | 342.02 | 1722.64 | 0.03 |
| QQQM | 6.38 | 254.43 | 1622.13 | 0.02 |
| VGT | 2.02 | 757.42 | 1529.25 | 0.02 |
| DIS | 13.03 | 114.07 | 1486.23 | 0.02 |
| VOO | 2.29 | 632.46 | 1447.28 | 0.02 |
| META | 1.58 | 658.79 | 1039.45 | 0.02 |
| PBR | 87.44 | 11.74 | 1026.60 | 0.01 |
| SOXX | 3.05 | 318.06 | 968.76 | 0.01 |
| CRWD | 1.46 | 456.55 | 664.40 | 0.01 |
| GOOGL | 2.01 | 316.54 | 636.79 | 0.01 |
| CME | 2.00 | 275.06 | 550.12 | 0.01 |
| GOOG | 1.61 | 317.32 | 512.10 | 0.01 |
| VUG | 1.01 | 488.45 | 492.50 | 0.01 |
| KO | 6.23 | 67.94 | 423.06 | 0.01 |
| QSR | 5.26 | 66.74 | 350.73 | 0.01 |
| ASML | 0.26 | 1228.19 | 324.45 | 0.00 |
| MCD | 1.05 | 299.86 | 315.95 | 0.00 |
| VYM | 2.10 | 145.82 | 306.92 | 0.00 |
| NU | 13.62 | 17.94 | 244.39 | 0.00 |
| OMAB | 2.16 | 109.26 | 236.47 | 0.00 |
| CAT | 0.33 | 616.10 | 205.93 | 0.00 |
| LMT | 0.32 | 511.57 | 163.25 | 0.00 |
| SPYD | 3.65 | 43.69 | 159.31 | 0.00 |
| SOFI | 5.32 | 29.28 | 155.76 | 0.00 |
| ADBE | 0.47 | 331.56 | 154.20 | 0.00 |
| MAR | 0.46 | 311.03 | 142.13 | 0.00 |
| BRK-B | 0.24 | 498.52 | 122.03 | 0.00 |
| NFLX | 1.20 | 91.46 | 109.50 | 0.00 |
| SPYG | 1.02 | 107.16 | 109.19 | 0.00 |
| BAC | 1.47 | 56.89 | 83.56 | 0.00 |
| VTV | 0.40 | 194.65 | 78.82 | 0.00 |
| PSA | 0.19 | 260.90 | 49.62 | 0.00 |
| SERV | 3.36 | 12.68 | 42.67 | 0.00 |
| SPG | 0.20 | 183.11 | 36.40 | 0.00 |
| HD | 0.02 | 344.09 | 7.21 | 0.00 |
TO-DO: Grafica de Pastel con los Porcentajes
Grafica por Sectores, Industrias
Portfolio Daily Returns¶
#checking the shapes of the objects to multiply
# Daily Returns Expresed in FRACTION.
print("Portfolio daily returns = [Daily Returns] x [weights]")
print(f"daily_returns shape: {daily_returns.shape}")
print(f"weights_df shape: {weights_df['Weights'].shape}")
print(f"Matrix multiplication [{daily_returns.shape[0]} x {daily_returns.shape[1]}] x [{weights_df['Weights'].shape[0]} x 1] = [{daily_returns.shape[0]} x 1]")
Portfolio daily returns = [Daily Returns] x [weights] daily_returns shape: (457, 45) weights_df shape: (45,) Matrix multiplication [457 x 45] x [45 x 1] = [457 x 1]
# option 1: Portfolio's Daily Returns: Matrix multiplication (see above)
portfolio_daily_returns = (daily_returns @ weights_df['Weights'])
# option 2: Portfolio's Daily Returns: weighted sum of the daily returns of each asset
# portfolio_daily_returns = (daily_returns * weights_df['Weights']).sum(axis=1)
portfolio_daily_returns.name = 'portfolio daily returns'
print("portfolio_daily_returns:")
display(portfolio_daily_returns) # Expresed in FRACTION (not percentage)
portfolio_daily_returns:
Date
2024-03-11 -0.009023
2024-03-12 0.027501
2024-03-13 -0.006010
2024-03-14 -0.009048
2024-03-15 -0.009670
...
2025-12-29 -0.008831
2025-12-30 -0.002100
2025-12-31 -0.006179
2026-01-02 0.000196
2026-01-05 0.005278
Name: portfolio daily returns, Length: 457, dtype: float64
results_df = functions.plot_returns_distributions(pd.DataFrame(portfolio_daily_returns), num_cols=4)
================================================================================ FAT-TAIL RISK RANKING (Sorted by Highest Kurtosis) ================================================================================
| Mean | Median | Std Dev | Skewness | Kurtosis | |
|---|---|---|---|---|---|
| Ticker | |||||
| portfolio daily returns | 0.0015 | 0.0025 | 0.0165 | 0.5720 | 10.2486 |
# -- PLOT DAILY RETURNS --
functions.plot_daily_returns(pd.DataFrame(portfolio_daily_returns))
Portfolio Annualized Returns¶
# Annualized Returns
annualized_return_portfolio = portfolio_daily_returns.mean() * 250
print("Annualized Returns (%):")
print(f"{round(annualized_return_portfolio, 2) * 100}%")
Annualized Returns (%): 38.0%
Portfolio Cumulative Returns¶
# Portfolio's Cumulative daily Returns
portfolio_cumulative_returns = (1 + portfolio_daily_returns).cumprod()
portfolio_cumulative_returns = (portfolio_cumulative_returns - 1) * 100
portfolio_cumulative_returns = portfolio_cumulative_returns.rename('portfolio cumulative returns')
print("Portfolio Daily Cumulative Returns:")
display(portfolio_cumulative_returns)
Portfolio Daily Cumulative Returns:
Date
2024-03-11 -0.902260
2024-03-12 1.823045
2024-03-13 1.211137
2024-03-14 0.295371
2024-03-15 -0.674509
...
2025-12-29 88.416738
2025-12-30 88.021052
2025-12-31 86.859277
2026-01-02 86.895959
2026-01-05 87.882465
Name: portfolio cumulative returns, Length: 457, dtype: float64
# -- PLOT CUMULATIVE RETURNS --
functions.plot_cumulative_returns(pd.DataFrame(portfolio_cumulative_returns))
Portfolio Cumulative Final
# Portfolio's final comulated return
portafolio_cumulative_final = portfolio_cumulative_returns.iloc[-1]
print(f"portafolio_cumulative Returns_final: {portafolio_cumulative_final:,.4}%")
portafolio_cumulative Returns_final: 87.88%
Portfolio Volatility (Annualized)¶
# Compute annualized Volatility of the Portfolio
portfolio_annualized_volatility = portfolio_daily_returns.std() * np.sqrt(252)
# Convert to percentage
portfolio_annualized_volatility_perc = portfolio_annualized_volatility * 100
print(f"Portfolio Annualized Volatility: {portfolio_annualized_volatility_perc:,.4}%")
Portfolio Annualized Volatility: 26.13%
Portfolio Sharpe Ratio¶
$$ Sharpe = \frac{R_p - R_f}{\sigma_p} = \frac{\mu_p - r_f}{\sigma_p}$$
Where:
- $R_p$: Expected Portfolio Return, $\mu$ (average rate of return)
- $R_f$: Risk Free Rate (can be 0 if ignored)
- $\sigma_p$: Portfolio Risk (StdDev) Standard Deviation of the portfolio's excess return.
portfolio_annualized_return = portfolio_daily_returns.mean()*252 #Annualized
portfolio_sr = round((portfolio_annualized_return - (risk_free))/portfolio_annualized_volatility, 2)
print(f"Portfolio Sharpe Ratio: {portfolio_sr}")
Portfolio Sharpe Ratio: 1.3
Summary¶
print(f"Number of days of evaluation: {(today - cumulative_returns.index[0]).days} days")
print(f"From {cumulative_returns.index[0].date().strftime("%B %d, %Y")} until {today.date().strftime("%B %d, %Y")}")
print(f"\nTotal Invested: ${Total_invested:,.2f}")
print(f"Portafolio_cumulative returns_final (all period): {portafolio_cumulative_final:,.4}%")
print(f"Portfolio Annualized Average Returns: {portfolio_annualized_return:,.4%}")
print(f"Portfolio Annualized Volatility: {portfolio_annualized_volatility_perc:,.4}%")
print(f"Portfolio Sharpe Ratio: {portfolio_sr}")
Number of days of evaluation: 666 days From March 11, 2024 until January 06, 2026 Total Invested: $68,831.74 Portafolio_cumulative returns_final (all period): 87.88% Portfolio Annualized Average Returns: 38.1843% Portfolio Annualized Volatility: 26.13% Portfolio Sharpe Ratio: 1.3
# Benchmark Indices
benchmark_indices = {
"EE.UU. (S&P 500)": "^GSPC",
"EE.UU. (NASDAQ)": "^IXIC",
"EE.UU. (DJIA)": "^DJI",
"EE.UU. (Russell 100)": "^RUI",
"México (IPC)": "^MXX",
"Japón (Nikkei 225)": "^N225",
"Alemania (DAX)": "^GDAXI",
"Reino Unido (FTSE 100)": "^FTSE"
}
# get data from yahoo finance
benchmarks_prices = yf.download(list(benchmark_indices.values()), start=first_valid_dates.iloc[0], end=today, auto_adjust=True)["Close"]
# Rename columns
benchmarks_prices.columns = list(benchmark_indices.keys())
print("Benchmark Indices levels:")
display(benchmarks_prices.tail(5))
[*********************100%***********************] 8 of 8 completed
Benchmark Indices levels:
| EE.UU. (S&P 500) | EE.UU. (NASDAQ) | EE.UU. (DJIA) | EE.UU. (Russell 100) | México (IPC) | Japón (Nikkei 225) | Alemania (DAX) | Reino Unido (FTSE 100) | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2025-12-29 | 48461.929688 | 9866.500000 | 24351.119141 | 6905.740234 | 23474.349609 | 65347.078125 | 50526.921875 | 3766.949951 |
| 2025-12-30 | 48367.058594 | 9940.700195 | 24490.410156 | 6896.240234 | 23419.080078 | 64366.699219 | 50339.480469 | 3761.540039 |
| 2025-12-31 | 48063.289062 | 9931.400391 | NaN | 6845.500000 | 23241.990234 | 64308.289062 | NaN | 3732.870117 |
| 2026-01-02 | 48382.390625 | 9951.099609 | 24539.339844 | 6858.470215 | 23235.630859 | 64141.359375 | NaN | 3742.669922 |
| 2026-01-05 | 48977.179688 | 10004.599609 | 24868.689453 | 6902.049805 | 23395.820312 | 65014.371094 | 51832.800781 | 3769.020020 |
# -- PLOT ASSET PRICES --
functions.plot_prices(prices=benchmarks_prices, yaxis_label="Index Level")
Normalized Index Levels¶
# Normalized Index Levels
benchmarks_prices_normalized = (benchmarks_prices / benchmarks_prices.iloc[0]) * 100
# -- PLOT ASSET PRICES --
functions.plot_prices(prices=benchmarks_prices_normalized, yaxis_label="Index Level")
Daily Returns¶
# Daily Returns
daily_returns_bm = benchmarks_prices.pct_change(fill_method=None)
# limpieza básica de daily_returns (elimina la primera fila con NaN)
daily_returns_bm = daily_returns_bm.dropna()
daily_returns_bm.tail(5)
| EE.UU. (S&P 500) | EE.UU. (NASDAQ) | EE.UU. (DJIA) | EE.UU. (Russell 100) | México (IPC) | Japón (Nikkei 225) | Alemania (DAX) | Reino Unido (FTSE 100) | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2025-12-18 | 0.001376 | 0.006497 | 0.009971 | 0.007934 | 0.013794 | 0.020459 | -0.010316 | 0.007753 |
| 2025-12-19 | 0.003817 | 0.006058 | 0.003674 | 0.008818 | 0.013095 | 0.002502 | 0.010320 | 0.008832 |
| 2025-12-22 | 0.004732 | -0.003173 | -0.000182 | 0.006436 | 0.005200 | 0.012682 | 0.018082 | 0.006793 |
| 2025-12-23 | 0.001649 | 0.002352 | 0.002310 | 0.004550 | 0.005677 | 0.012616 | 0.000208 | 0.003815 |
| 2025-12-30 | -0.001958 | 0.007520 | 0.005720 | -0.001376 | -0.002354 | -0.015003 | -0.003710 | -0.001436 |
results_df = functions.plot_returns_distributions(daily_returns_bm, num_cols=4)
================================================================================ FAT-TAIL RISK RANKING (Sorted by Highest Kurtosis) ================================================================================
| Mean | Median | Std Dev | Skewness | Kurtosis | |
|---|---|---|---|---|---|
| Ticker | |||||
| EE.UU. (Russell 100) | 0.0007 | 0.0011 | 0.0108 | 0.8268 | 19.6195 |
| Reino Unido (FTSE 100) | 0.0007 | 0.0012 | 0.0110 | 0.8020 | 19.3017 |
| EE.UU. (S&P 500) | 0.0004 | 0.0007 | 0.0096 | 0.7444 | 15.7375 |
| México (IPC) | 0.0011 | 0.0022 | 0.0144 | 0.8647 | 15.0550 |
| Alemania (DAX) | 0.0006 | 0.0007 | 0.0172 | -0.4420 | 14.0991 |
| EE.UU. (NASDAQ) | 0.0005 | 0.0008 | 0.0074 | -1.3876 | 10.4678 |
| Japón (Nikkei 225) | 0.0003 | 0.0003 | 0.0101 | -0.6549 | 5.5900 |
| EE.UU. (DJIA) | 0.0007 | 0.0006 | 0.0104 | -0.3767 | 3.3813 |
functions.plot_daily_returns(daily_returns_bm)
Annualized Returns¶
# Annualized Returns
annualized_return_bm = daily_returns_bm.mean() * 252
annualized_return_bm.name = "Annualized Returns (%):"
annualized_return_bm = annualized_return_bm.sort_values(ascending=False)
print("Annualized Returns (%):")
print(round(annualized_return_bm, 2)*100)
Annualized Returns (%): México (IPC) 27.0 EE.UU. (DJIA) 17.0 EE.UU. (Russell 100) 17.0 Reino Unido (FTSE 100) 16.0 Alemania (DAX) 15.0 EE.UU. (NASDAQ) 13.0 EE.UU. (S&P 500) 11.0 Japón (Nikkei 225) 8.0 Name: Annualized Returns (%):, dtype: float64
Cumulative Daily Returns¶
cumulative_returns_bm = (1 + daily_returns_bm).cumprod()
cumulative_returns_bm = (cumulative_returns_bm - 1) * 100
display(cumulative_returns_bm.tail(5))
| EE.UU. (S&P 500) | EE.UU. (NASDAQ) | EE.UU. (DJIA) | EE.UU. (Russell 100) | México (IPC) | Japón (Nikkei 225) | Alemania (DAX) | Reino Unido (FTSE 100) | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2025-12-18 | 13.266130 | 17.128699 | 23.859387 | 22.352113 | 37.085787 | 8.299930 | 14.406983 | 21.228400 |
| 2025-12-19 | 13.698483 | 17.838302 | 24.314403 | 23.431021 | 38.880875 | 8.570921 | 15.587699 | 22.299108 |
| 2025-12-22 | 14.236539 | 17.464450 | 24.291731 | 24.225485 | 39.603123 | 9.947766 | 17.677733 | 23.129847 |
| 2025-12-23 | 14.424869 | 17.740671 | 24.578813 | 24.790759 | 40.395675 | 11.334842 | 17.702203 | 23.599630 |
| 2025-12-30 | 14.200866 | 18.626130 | 25.291417 | 24.619089 | 40.065119 | 9.664525 | 17.265559 | 23.422122 |
functions.plot_cumulative_returns(cumulative_returns_bm)
# Final Cumulative Return in the period of evaluation for all Indices
print("Final Cumulative Returns (%)")
# cumulative_returns_final_bm = (1 + daily_returns_bm).prod() -1
cumulative_returns_final_bm = cumulative_returns_bm.iloc[-1]
cumulative_returns_final_bm = cumulative_returns_final_bm.sort_values(ascending=False)
cumulative_returns_final_bm = cumulative_returns_final_bm.rename("final cumulative returns Benchmarks (%)")
display(cumulative_returns_final_bm)
Final Cumulative Returns (%)
México (IPC) 40.065119 EE.UU. (DJIA) 25.291417 EE.UU. (Russell 100) 24.619089 Reino Unido (FTSE 100) 23.422122 EE.UU. (NASDAQ) 18.626130 Alemania (DAX) 17.265559 EE.UU. (S&P 500) 14.200866 Japón (Nikkei 225) 9.664525 Name: final cumulative returns Benchmarks (%), dtype: float64
Volatility¶
#Annualized Volatility
annualized_volatility_bm = daily_returns_bm.std() * np.sqrt(252)
#Annualized Volatility Percentage
annualized_volatility_bm_perc = annualized_volatility_bm * 100
annualized_volatility_bm_perc.rename('Volatility Benchmarks (%)', inplace=True)
print("Volatility of Benchmark Indices (%):")
annualized_volatility_bm_perc.sort_values(ascending=False, inplace=True)
display(annualized_volatility_bm_perc)
Volatility of Benchmark Indices (%):
Alemania (DAX) 27.297079 México (IPC) 22.904904 Reino Unido (FTSE 100) 17.402071 EE.UU. (Russell 100) 17.223190 EE.UU. (DJIA) 16.435356 Japón (Nikkei 225) 15.980321 EE.UU. (S&P 500) 15.286125 EE.UU. (NASDAQ) 11.685753 Name: Volatility Benchmarks (%), dtype: float64
Sharpe Ratios¶
$$ Sharpe = \frac{R_p - R_f}{\sigma_p} = \frac{\mu_p - r_f}{\sigma_p}$$
Where:
- $R_p$: Expected Portfolio Return, $\mu$
- $R_f$: Risk Free Rate (can be 0 if ignored)
- $\sigma_p$: Portfolio Risk (StdDev)
# Sharpe Ratios
benchmark_sr = round((annualized_return_bm - (risk_free))/annualized_volatility_bm, 2)
benchmark_sr.rename('Sharpe Ratio', inplace=True)
benchmark_sr.sort_values(ascending=False, inplace=True)
display(benchmark_sr)
México (IPC) 0.98 EE.UU. (DJIA) 0.81 EE.UU. (Russell 100) 0.75 EE.UU. (NASDAQ) 0.74 Reino Unido (FTSE 100) 0.71 EE.UU. (S&P 500) 0.42 Alemania (DAX) 0.40 Japón (Nikkei 225) 0.23 Name: Sharpe Ratio, dtype: float64
Summary¶
# Days of evaluation
no_days_compare = today.date() - first_valid_dates.iloc[0].date()
print(f"\nNumber of days of evaluation: {no_days_compare.days} days.")
print(f"From: {first_valid_dates.iloc[0].date().strftime("%B %d, %Y")} to: {today.date().strftime("%B %d, %Y")}")
# Summary Return and Volatility
print("\nSummary: Benchmark Indices:")
print(f"'{cumulative_returns_final_bm.index[0]}' has the largest total return {cumulative_returns_final_bm.iloc[0]:,.5}%")
print(f"and'{cumulative_returns_final_bm.index[-1]}' the lowest total return {cumulative_returns_final_bm.iloc[-1]:,.3}%")
print(f"\n'{annualized_volatility_bm_perc.index[0]}' has the largest volatility {annualized_volatility_bm_perc.iloc[0]:,.5}%")
print(f"and '{annualized_volatility_bm_perc.index[-1]}' the lowest volatility {annualized_volatility_bm_perc.iloc[-1]:,.5}%")
Number of days of evaluation: 669 days. From: March 08, 2024 to: January 06, 2026 Summary: Benchmark Indices: 'México (IPC)' has the largest total return 40.065% and'Japón (Nikkei 225)' the lowest total return 9.66% 'Alemania (DAX)' has the largest volatility 27.297% and 'EE.UU. (NASDAQ)' the lowest volatility 11.686%
Compare Initial Portfolio and Indices¶
Daily Returns¶
Combine dataframes
# Combine Daily Returns of Initial Porfolio and
# Benchmark Indices in a single dataFrame to plot
# convert portfolio_daily_returns to datafre to merge it with Indices daily returns
portfolio_daily_returns_df = pd.DataFrame(portfolio_daily_returns)
portfolio_daily_returns_df.rename(columns={'portfolio daily returns':'Initial Portfolio'}, inplace=True)
# check if the lenghts of the dataframes match
if len(portfolio_daily_returns_df) != len(daily_returns_bm):
print(f"Lengths of DataFrames don't match {len(portfolio_daily_returns_df)} vs {len(daily_returns_bm)} but a left merge will help")
print("There are more dates in one dataframe than the other")
# Merge Daily Returns of Benchmark Indices and Initial Portfolio
# Note: Left-Merge on Benchmark daily returns because they don't operate on weekends or bank holidays
# thus we compare both benchmark and portfolio using the same labor day dates only.
merge_daily_returns = pd.merge(daily_returns_bm, portfolio_daily_returns_df, on='Date', how="left")
print("\nDaily Returns (merged portfolio and indices):")
display(merge_daily_returns.tail(5))
Lengths of DataFrames don't match 457 vs 354 but a left merge will help There are more dates in one dataframe than the other Daily Returns (merged portfolio and indices):
| EE.UU. (S&P 500) | EE.UU. (NASDAQ) | EE.UU. (DJIA) | EE.UU. (Russell 100) | México (IPC) | Japón (Nikkei 225) | Alemania (DAX) | Reino Unido (FTSE 100) | Initial Portfolio | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 2025-12-18 | 0.001376 | 0.006497 | 0.009971 | 0.007934 | 0.013794 | 0.020459 | -0.010316 | 0.007753 | 0.014941 |
| 2025-12-19 | 0.003817 | 0.006058 | 0.003674 | 0.008818 | 0.013095 | 0.002502 | 0.010320 | 0.008832 | 0.017300 |
| 2025-12-22 | 0.004732 | -0.003173 | -0.000182 | 0.006436 | 0.005200 | 0.012682 | 0.018082 | 0.006793 | 0.007409 |
| 2025-12-23 | 0.001649 | 0.002352 | 0.002310 | 0.004550 | 0.005677 | 0.012616 | 0.000208 | 0.003815 | 0.011454 |
| 2025-12-30 | -0.001958 | 0.007520 | 0.005720 | -0.001376 | -0.002354 | -0.015003 | -0.003710 | -0.001436 | -0.002100 |
results_df = functions.plot_returns_distributions(merge_daily_returns, num_cols=4)
================================================================================ FAT-TAIL RISK RANKING (Sorted by Highest Kurtosis) ================================================================================
| Mean | Median | Std Dev | Skewness | Kurtosis | |
|---|---|---|---|---|---|
| Ticker | |||||
| EE.UU. (Russell 100) | 0.0007 | 0.0011 | 0.0108 | 0.8268 | 19.6195 |
| Reino Unido (FTSE 100) | 0.0007 | 0.0012 | 0.0110 | 0.8020 | 19.3017 |
| EE.UU. (S&P 500) | 0.0004 | 0.0007 | 0.0096 | 0.7444 | 15.7375 |
| México (IPC) | 0.0011 | 0.0022 | 0.0144 | 0.8647 | 15.0550 |
| Alemania (DAX) | 0.0006 | 0.0007 | 0.0172 | -0.4420 | 14.0991 |
| Initial Portfolio | 0.0019 | 0.0037 | 0.0172 | 0.6294 | 10.9811 |
| EE.UU. (NASDAQ) | 0.0005 | 0.0008 | 0.0074 | -1.3876 | 10.4678 |
| Japón (Nikkei 225) | 0.0003 | 0.0003 | 0.0101 | -0.6549 | 5.5900 |
| EE.UU. (DJIA) | 0.0007 | 0.0006 | 0.0104 | -0.3767 | 3.3813 |
functions.plot_daily_returns(merge_daily_returns)
Cumulative Returns¶
# Combine Comulative Returns of Initial Porfolio and Benchmark Indices in a single dataFrame to plot
# convert portfolio_cumulative_returns to datafre to merge it with Indices returns
portfolio_cumulative_returns_df = pd.DataFrame(portfolio_cumulative_returns)
portfolio_cumulative_returns_df.rename(columns={'portfolio cumulative returns':'Initial Portfolio'}, inplace=True)
# check if the lenghts of the dataframes match
if len(portfolio_cumulative_returns_df) != len(cumulative_returns_bm):
print(f"Lengths of DataFrames don't match {len(portfolio_cumulative_returns_df)} vs {len(cumulative_returns_bm)} but a left merge will help")
# Merge Cumulative Returns of Benchmark Indices and Initial Portfolio
# Note: Left-Merge on Benchmark cumulative returns because they don't operate on weekends or bank holidays
# thus we compare both benchmark and portfolio using the same labor day dates only.
merge_cumulative_returns = pd.merge(cumulative_returns_bm, portfolio_cumulative_returns_df, on='Date', how="left")
print("\nDaily Cumulative Returns (merged portfolio and indices):")
display(merge_cumulative_returns.tail(5))
Lengths of DataFrames don't match 457 vs 354 but a left merge will help Daily Cumulative Returns (merged portfolio and indices):
| EE.UU. (S&P 500) | EE.UU. (NASDAQ) | EE.UU. (DJIA) | EE.UU. (Russell 100) | México (IPC) | Japón (Nikkei 225) | Alemania (DAX) | Reino Unido (FTSE 100) | Initial Portfolio | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 2025-12-18 | 13.266130 | 17.128699 | 23.859387 | 22.352113 | 37.085787 | 8.299930 | 14.406983 | 21.228400 | 82.948254 |
| 2025-12-19 | 13.698483 | 17.838302 | 24.314403 | 23.431021 | 38.880875 | 8.570921 | 15.587699 | 22.299108 | 86.113241 |
| 2025-12-22 | 14.236539 | 17.464450 | 24.291731 | 24.225485 | 39.603123 | 9.947766 | 17.677733 | 23.129847 | 87.492159 |
| 2025-12-23 | 14.424869 | 17.740671 | 24.578813 | 24.790759 | 40.395675 | 11.334842 | 17.702203 | 23.599630 | 89.639700 |
| 2025-12-30 | 14.200866 | 18.626130 | 25.291417 | 24.619089 | 40.065119 | 9.664525 | 17.265559 | 23.422122 | 88.021052 |
functions.plot_cumulative_returns(merge_cumulative_returns)
# Total Cumulative Returns
print("\nTotal (Final) cumulative Returns (%):")
merge_cumulative_returns_finals = merge_cumulative_returns.iloc[-1].copy()
merge_cumulative_returns_finals.rename('Final Cumulative Returns (%)', inplace=True)
merge_cumulative_returns_finals.sort_values(ascending=False, inplace=True)
display(merge_cumulative_returns_finals)
Total (Final) cumulative Returns (%):
Initial Portfolio 88.021052 México (IPC) 40.065119 EE.UU. (DJIA) 25.291417 EE.UU. (Russell 100) 24.619089 Reino Unido (FTSE 100) 23.422122 EE.UU. (NASDAQ) 18.626130 Alemania (DAX) 17.265559 EE.UU. (S&P 500) 14.200866 Japón (Nikkei 225) 9.664525 Name: Final Cumulative Returns (%), dtype: float64
Volatility¶
# Anualized Volatility of portfolio + Benchmarks
print('\nAnnualized Volatility (%):')
merge_annualized_volatility = merge_daily_returns.std() * np.sqrt(252)
merge_annualized_volatility = merge_annualized_volatility * 100
merge_annualized_volatility.rename('Annualized Volatility (%)', inplace=True)
merge_annualized_volatility.sort_values(ascending=False, inplace=True)
display(merge_annualized_volatility)
Annualized Volatility (%):
Alemania (DAX) 27.297079 Initial Portfolio 27.296752 México (IPC) 22.904904 Reino Unido (FTSE 100) 17.402071 EE.UU. (Russell 100) 17.223190 EE.UU. (DJIA) 16.435356 Japón (Nikkei 225) 15.980321 EE.UU. (S&P 500) 15.286125 EE.UU. (NASDAQ) 11.685753 Name: Annualized Volatility (%), dtype: float64
Sharpe Ratio¶
sharpe_ratio = pd.DataFrame()
sharpe_ratio['Annualized Returns (%)'] = merge_daily_returns.mean()*252*100 #Annualized returns
sharpe_ratio = pd.concat([sharpe_ratio['Annualized Returns (%)'], merge_annualized_volatility], axis=1)
sharpe_ratio['Sharpe Ratio'] = (sharpe_ratio['Annualized Returns (%)'] - (risk_free*100)) / (sharpe_ratio['Annualized Volatility (%)'])
sharpe_ratio.sort_values(by='Sharpe Ratio', ascending=False, inplace=True)
sharpe_ratio
| Annualized Returns (%) | Annualized Volatility (%) | Sharpe Ratio | |
|---|---|---|---|
| Initial Portfolio | 48.292658 | 27.296752 | 1.617066 |
| México (IPC) | 26.592593 | 22.904904 | 0.979729 |
| EE.UU. (DJIA) | 17.405433 | 16.435356 | 0.806398 |
| EE.UU. (Russell 100) | 17.143433 | 17.223190 | 0.754299 |
| EE.UU. (NASDAQ) | 12.847404 | 11.685753 | 0.744103 |
| Reino Unido (FTSE 100) | 16.486908 | 17.402071 | 0.708818 |
| EE.UU. (S&P 500) | 10.614472 | 15.286125 | 0.422767 |
| Alemania (DAX) | 15.081337 | 27.297079 | 0.400385 |
| Japón (Nikkei 225) | 7.847153 | 15.980321 | 0.231231 |
TO-do: Poner nota explicatoria de por que el SR es 1.58 aqui contra 1.3 arriba
#fig, ax = plt.subplots()
ax = sharpe_ratio.plot.scatter(
x=sharpe_ratio.columns[1],
y=sharpe_ratio.columns[0],
c=sharpe_ratio.columns[2],
colormap='coolwarm',
alpha=1.0,
figsize=(10,8))
for i, label in enumerate(round(sharpe_ratio['Sharpe Ratio'],3)):
ax.text(sharpe_ratio['Annualized Volatility (%)'].iloc[i] + 0.05, sharpe_ratio['Annualized Returns (%)'].iloc[i], label)
ax.text(sharpe_ratio['Annualized Volatility (%)'].iloc[i] + 0.05, sharpe_ratio['Annualized Returns (%)'].iloc[i] + 1, sharpe_ratio.index[i])
ax.figure.axes[1].set_ylabel('Sharpe Ratio level')
plt.title('Sharpe Ratio')
plt.xlabel('Risk (%)')
plt.ylabel('Return (%)')
plt.grid()
plt.show()
Summary¶
# Days of evaluation
no_days_compare = today.date() - first_valid_dates.iloc[0].date()
print(f"\nNumber of days of evaluation: {no_days_compare.days} days. From: {first_valid_dates.iloc[0].date().strftime("%B %d, %Y")} to: {today.date().strftime("%B %d, %Y")}")
# Summary Return, Volatility and Sharp Ratio
print("\nSummary: Benchmark Indices + Initial Portfolio")
print("\nReturns:")
print(f"'{merge_cumulative_returns_finals.index[0]}' has the largest total return {merge_cumulative_returns_finals.iloc[0]:,.5}%")
print(f"and'{merge_cumulative_returns_finals.index[-1]}' the lowest total return {merge_cumulative_returns_finals.iloc[-1]:,.3}%")
print("\nVolatility:")
print(f"'{merge_annualized_volatility.index[0]}' has the largest volatility {merge_annualized_volatility.iloc[0]:,.5}%")
print(f"and '{merge_annualized_volatility.index[-1]}' the lowest volatility {merge_annualized_volatility.iloc[-1]:,.5}%")
print("\nSharp Ratio:")
print(f"'{sharpe_ratio.index[0]}' has the best Sharpe Ratio {sharpe_ratio['Sharpe Ratio'].iloc[0]:,.4}")
print(f"and '{sharpe_ratio.index[-1]}' the worst Sharp Ratio {sharpe_ratio['Sharpe Ratio'].iloc[-1]:,.3}")
Number of days of evaluation: 669 days. From: March 08, 2024 to: January 06, 2026 Summary: Benchmark Indices + Initial Portfolio Returns: 'Initial Portfolio' has the largest total return 88.021% and'Japón (Nikkei 225)' the lowest total return 9.66% Volatility: 'Alemania (DAX)' has the largest volatility 27.297% and 'EE.UU. (NASDAQ)' the lowest volatility 11.686% Sharp Ratio: 'Initial Portfolio' has the best Sharpe Ratio 1.617 and 'Japón (Nikkei 225)' the worst Sharp Ratio 0.231
Buy and Hold¶
B&H - Stocks (Initial Portfolio)¶
Initial Investment $100,000 in each Asset
# B&H Stocks in initial Portfolio
functions.buy_and_hold_strategy(cumulative_returns, 100000, "Portfolio Stocks")
--- Investment Analysis: Portfolio Stocks --- From: March 11, 2024 to January 05, 2026 Period: 665 days
| Initial_Value_USD | Final_Value_USD | Margin_USD | Yield_Perc | |
|---|---|---|---|---|
| Ticker | ||||
| PLTR | 100,000.00 | 668,356.33 | 568,356.33 | 568.36 |
| SOFI | 100,000.00 | 379,766.54 | 279,766.54 | 279.77 |
| TSLA | 100,000.00 | 257,596.68 | 157,596.68 | 157.60 |
| GOOGL | 100,000.00 | 235,530.37 | 135,530.37 | 135.53 |
| GOOG | 100,000.00 | 234,573.32 | 134,573.32 | 134.57 |
| TSM | 100,000.00 | 226,286.44 | 126,286.44 | 126.29 |
| NVDA | 100,000.00 | 215,035.17 | 115,035.17 | 115.04 |
| GLD | 100,000.00 | 202,727.77 | 102,727.77 | 102.73 |
| CAT | 100,000.00 | 186,474.88 | 86,474.88 | 86.47 |
| OMAB | 100,000.00 | 179,496.09 | 79,496.09 | 79.50 |
| BAC | 100,000.00 | 166,492.49 | 66,492.49 | 66.49 |
| NU | 100,000.00 | 162,059.63 | 62,059.63 | 62.06 |
| AAPL | 100,000.00 | 157,816.54 | 57,816.54 | 57.82 |
| NFLX | 100,000.00 | 151,218.55 | 51,218.55 | 51.22 |
| SPYG | 100,000.00 | 151,135.28 | 51,135.28 | 51.14 |
| VOOG | 100,000.00 | 151,061.84 | 51,061.84 | 51.06 |
| VGT | 100,000.00 | 146,845.11 | 46,845.11 | 46.85 |
| VUG | 100,000.00 | 145,287.56 | 45,287.56 | 45.29 |
| QQQM | 100,000.00 | 142,482.23 | 42,482.23 | 42.48 |
| CRWD | 100,000.00 | 141,412.41 | 41,412.41 | 41.41 |
| SOXX | 100,000.00 | 141,291.84 | 41,291.84 | 41.29 |
| VOO | 100,000.00 | 137,898.04 | 37,898.04 | 37.90 |
| CME | 100,000.00 | 137,022.77 | 37,022.77 | 37.02 |
| SPG | 100,000.00 | 132,267.28 | 32,267.28 | 32.27 |
| VYM | 100,000.00 | 131,208.38 | 31,208.38 | 31.21 |
| META | 100,000.00 | 130,979.08 | 30,979.08 | 30.98 |
| VTV | 100,000.00 | 128,760.30 | 28,760.30 | 28.76 |
| MAR | 100,000.00 | 126,464.20 | 26,464.20 | 26.46 |
| ASML | 100,000.00 | 125,481.10 | 25,481.10 | 25.48 |
| QYLD | 100,000.00 | 124,332.93 | 24,332.93 | 24.33 |
| LMT | 100,000.00 | 123,931.91 | 23,931.91 | 23.93 |
| BRK-B | 100,000.00 | 123,656.21 | 23,656.21 | 23.66 |
| VOOV | 100,000.00 | 122,792.89 | 22,792.89 | 22.79 |
| KO | 100,000.00 | 121,103.27 | 21,103.27 | 21.10 |
| SPYD | 100,000.00 | 119,553.37 | 19,553.37 | 19.55 |
| MSFT | 100,000.00 | 117,919.92 | 17,919.92 | 17.92 |
| MCD | 100,000.00 | 106,823.52 | 6,823.52 | 6.82 |
| DIS | 100,000.00 | 105,490.38 | 5,490.38 | 5.49 |
| PBR | 100,000.00 | 103,522.28 | 3,522.28 | 3.52 |
| PSA | 100,000.00 | 96,453.83 | -3,546.17 | -3.55 |
| HD | 100,000.00 | 96,198.91 | -3,801.09 | -3.80 |
| QSR | 100,000.00 | 89,795.70 | -10,204.30 | -10.20 |
| UNH | 100,000.00 | 74,392.43 | -25,607.57 | -25.61 |
| ADBE | 100,000.00 | 60,098.97 | -39,901.03 | -39.90 |
| SERV | 100,000.00 | 53,277.31 | -46,722.69 | -46.72 |
B&H - Indices and Portfolio¶
Initial Investment $100,000 in each Index and Initial Portfolio
# B&H Indices & Portfolio (merged)
functions.buy_and_hold_strategy(merge_cumulative_returns, 100000, "Market Benchmarks Indices and Initital Portfolio")
--- Investment Analysis: Market Benchmarks Indices and Initital Portfolio --- From: March 11, 2024 to December 30, 2025 Period: 659 days
| Initial_Value_USD | Final_Value_USD | Margin_USD | Yield_Perc | |
|---|---|---|---|---|
| Ticker | ||||
| Initial Portfolio | 100,000.00 | 188,021.05 | 88,021.05 | 88.02 |
| México (IPC) | 100,000.00 | 140,065.12 | 40,065.12 | 40.07 |
| EE.UU. (DJIA) | 100,000.00 | 125,291.42 | 25,291.42 | 25.29 |
| EE.UU. (Russell 100) | 100,000.00 | 124,619.09 | 24,619.09 | 24.62 |
| Reino Unido (FTSE 100) | 100,000.00 | 123,422.12 | 23,422.12 | 23.42 |
| EE.UU. (NASDAQ) | 100,000.00 | 118,626.13 | 18,626.13 | 18.63 |
| Alemania (DAX) | 100,000.00 | 117,265.56 | 17,265.56 | 17.27 |
| EE.UU. (S&P 500) | 100,000.00 | 114,200.87 | 14,200.87 | 14.20 |
| Japón (Nikkei 225) | 100,000.00 | 109,664.53 | 9,664.53 | 9.66 |
# For individual stocks
functions.dollar_cost_averaging_strategy(prices, monthly_investment=100, title_suffix="Individual Stocks")
--- DCA Investment Analysis: Individual Stocks ---
| Total_Invested | Final_Value_USD | Margin_USD | Yield_Perc_% | |
|---|---|---|---|---|
| Ticker | ||||
| PLTR | 2,300.00 | 7,347.91 | 5,047.91 | 219.47 |
| SOFI | 2,300.00 | 5,477.07 | 3,177.07 | 138.13 |
| SERV | 2,300.00 | 4,211.12 | 1,911.12 | 83.09 |
| GOOGL | 2,300.00 | 3,851.00 | 1,551.00 | 67.43 |
| TSM | 2,300.00 | 3,837.66 | 1,537.66 | 66.85 |
| GOOG | 2,300.00 | 3,831.20 | 1,531.20 | 66.57 |
| CAT | 2,300.00 | 3,662.59 | 1,362.59 | 59.24 |
| TSLA | 2,300.00 | 3,647.39 | 1,347.39 | 58.58 |
| GLD | 2,300.00 | 3,449.93 | 1,149.93 | 50.00 |
| ASML | 2,300.00 | 3,437.25 | 1,137.25 | 49.45 |
| NVDA | 2,300.00 | 3,268.31 | 968.31 | 42.10 |
| OMAB | 2,300.00 | 3,194.73 | 894.73 | 38.90 |
| SOXX | 2,300.00 | 3,152.16 | 852.16 | 37.05 |
| NU | 2,300.00 | 3,143.96 | 843.96 | 36.69 |
| BAC | 2,300.00 | 2,997.71 | 697.71 | 30.34 |
| VGT | 2,300.00 | 2,833.99 | 533.99 | 23.22 |
| CRWD | 2,300.00 | 2,833.33 | 533.33 | 23.19 |
| VOOG | 2,300.00 | 2,820.17 | 520.17 | 22.62 |
| SPYG | 2,300.00 | 2,820.01 | 520.01 | 22.61 |
| QQQM | 2,300.00 | 2,775.69 | 475.69 | 20.68 |
| VUG | 2,300.00 | 2,769.29 | 469.29 | 20.40 |
| MAR | 2,300.00 | 2,760.62 | 460.62 | 20.03 |
| AAPL | 2,300.00 | 2,754.08 | 454.08 | 19.74 |
| VOO | 2,300.00 | 2,703.26 | 403.26 | 17.53 |
| CME | 2,300.00 | 2,692.53 | 392.53 | 17.07 |
| VYM | 2,300.00 | 2,632.25 | 332.25 | 14.45 |
| SPG | 2,300.00 | 2,629.02 | 329.02 | 14.31 |
| VTV | 2,300.00 | 2,616.17 | 316.17 | 13.75 |
| QYLD | 2,300.00 | 2,601.34 | 301.34 | 13.10 |
| META | 2,300.00 | 2,578.94 | 278.94 | 12.13 |
| VOOV | 2,300.00 | 2,568.62 | 268.62 | 11.68 |
| LMT | 2,300.00 | 2,491.15 | 191.15 | 8.31 |
| MSFT | 2,300.00 | 2,480.81 | 180.81 | 7.86 |
| DIS | 2,300.00 | 2,471.46 | 171.46 | 7.45 |
| BRK-B | 2,300.00 | 2,434.09 | 134.09 | 5.83 |
| NFLX | 2,300.00 | 2,431.42 | 131.42 | 5.71 |
| SPYD | 2,300.00 | 2,426.08 | 126.08 | 5.48 |
| MCD | 2,300.00 | 2,402.63 | 102.63 | 4.46 |
| KO | 2,300.00 | 2,378.96 | 78.96 | 3.43 |
| QSR | 2,300.00 | 2,325.70 | 25.70 | 1.12 |
| PBR | 2,300.00 | 2,209.65 | -90.35 | -3.93 |
| HD | 2,300.00 | 2,166.31 | -133.69 | -5.81 |
| PSA | 2,300.00 | 2,114.69 | -185.31 | -8.06 |
| UNH | 2,300.00 | 1,932.03 | -367.97 | -16.00 |
| ADBE | 2,300.00 | 1,828.83 | -471.17 | -20.49 |
DCA - Indices¶
Monthly Investment $100 in each Asset
# For Benchmarks and your Portfolio Index
functions.dollar_cost_averaging_strategy(benchmarks_prices, monthly_investment=100, title_suffix="Indices & Portfolio")
--- DCA Investment Analysis: Indices & Portfolio ---
| Total_Invested | Final_Value_USD | Margin_USD | Yield_Perc_% | |
|---|---|---|---|---|
| Ticker | ||||
| Alemania (DAX) | 2,300.00 | 2,922.82 | 622.82 | 27.08 |
| México (IPC) | 2,300.00 | 2,802.21 | 502.21 | 21.84 |
| EE.UU. (DJIA) | 2,300.00 | 2,696.83 | 396.83 | 17.25 |
| Japón (Nikkei 225) | 2,300.00 | 2,679.46 | 379.46 | 16.50 |
| EE.UU. (Russell 100) | 2,300.00 | 2,671.80 | 371.80 | 16.17 |
| Reino Unido (FTSE 100) | 2,300.00 | 2,667.82 | 367.82 | 15.99 |
| EE.UU. (NASDAQ) | 2,300.00 | 2,642.57 | 342.57 | 14.89 |
| EE.UU. (S&P 500) | 2,300.00 | 2,616.49 | 316.49 | 13.76 |
Momentum¶
Applied to all Stocks in Initial Portfolio
# call the DataFrame of the Final Cumulative Returns, which is the total return of each asset in the period.
cumulative_returns_final
# MOMENTUM: Order from largest to lowest return
momentum_ranking = cumulative_returns_final.sort_values(ascending=False)
print("Momentum Ranking (Annual yield):")
display(momentum_ranking)
# Inverse Ranking
inverse_ranking = cumulative_returns_final.sort_values(ascending=True)
print("Inverse Momentum Ranking (Annual yield):")
display(inverse_ranking)
# Plot Momentum and Inverse momentum
plt.Figure(figsize=(12, 9))
momentum_ranking.plot(kind='bar', title='Momentum Ranking')
plt.ylabel('Yield (%)')
plt.grid(True)
plt.tight_layout()
plt.show()
plt.Figure(figsize=(12, 9))
inverse_ranking.plot(kind='bar', title='Inverse Ranking', color='red')
plt.ylabel('Yield (%)')
plt.grid(True)
plt.tight_layout()
plt.show()
Momentum Ranking (Annual yield):
Ticker PLTR 568.4 SOFI 279.8 TSLA 157.6 GOOGL 135.5 GOOG 134.6 TSM 126.3 NVDA 115.0 GLD 102.7 CAT 86.5 OMAB 79.5 BAC 66.5 NU 62.1 AAPL 57.8 NFLX 51.2 SPYG 51.1 VOOG 51.1 VGT 46.8 VUG 45.3 QQQM 42.5 CRWD 41.4 SOXX 41.3 VOO 37.9 CME 37.0 SPG 32.3 VYM 31.2 META 31.0 VTV 28.8 MAR 26.5 ASML 25.5 QYLD 24.3 LMT 23.9 BRK-B 23.7 VOOV 22.8 KO 21.1 SPYD 19.6 MSFT 17.9 MCD 6.8 DIS 5.5 PBR 3.5 PSA -3.5 HD -3.8 QSR -10.2 UNH -25.6 ADBE -39.9 SERV -46.7 Name: Final Cumulative Returns (%), dtype: float64
Inverse Momentum Ranking (Annual yield):
Ticker SERV -46.7 ADBE -39.9 UNH -25.6 QSR -10.2 HD -3.8 PSA -3.5 PBR 3.5 DIS 5.5 MCD 6.8 MSFT 17.9 SPYD 19.6 KO 21.1 VOOV 22.8 BRK-B 23.7 LMT 23.9 QYLD 24.3 ASML 25.5 MAR 26.5 VTV 28.8 META 31.0 VYM 31.2 SPG 32.3 CME 37.0 VOO 37.9 SOXX 41.3 CRWD 41.4 QQQM 42.5 VUG 45.3 VGT 46.8 SPYG 51.1 VOOG 51.1 NFLX 51.2 AAPL 57.8 NU 62.1 BAC 66.5 OMAB 79.5 CAT 86.5 GLD 102.7 NVDA 115.0 TSM 126.3 GOOG 134.6 GOOGL 135.5 TSLA 157.6 SOFI 279.8 PLTR 568.4 Name: Final Cumulative Returns (%), dtype: float64
Applied to Indices
# call the DataFrame of the Final Cumulative Returns, which is the total return of each Index in the period.
cumulative_returns_final_bm
# MOMENTUM: Order from largest to lowest return
momentum_ranking_bm = cumulative_returns_final_bm.sort_values(ascending=False)
print("Momentum Ranking (Annual yield) of Benchmark Indices:")
display(momentum_ranking_bm)
# Inverse Ranking
inverse_ranking_bm = cumulative_returns_final_bm.sort_values(ascending=True)
print("Inverse Momentum Ranking (Annual yield) of Benchmark Indices:")
display(inverse_ranking_bm)
# Plot Momentum and Inverse momentum
plt.Figure(figsize=(12, 9))
momentum_ranking_bm.plot(kind='bar', title='Momentum Ranking')
plt.ylabel('Yield (%)')
plt.grid(True)
plt.tight_layout()
plt.show()
plt.Figure(figsize=(12, 9))
inverse_ranking_bm.plot(kind='bar', title='Inverse Ranking', color='red')
plt.ylabel('Yield (%)')
plt.grid(True)
plt.tight_layout()
plt.show()
Momentum Ranking (Annual yield) of Benchmark Indices:
México (IPC) 40.065119 EE.UU. (DJIA) 25.291417 EE.UU. (Russell 100) 24.619089 Reino Unido (FTSE 100) 23.422122 EE.UU. (NASDAQ) 18.626130 Alemania (DAX) 17.265559 EE.UU. (S&P 500) 14.200866 Japón (Nikkei 225) 9.664525 Name: final cumulative returns Benchmarks (%), dtype: float64
Inverse Momentum Ranking (Annual yield) of Benchmark Indices:
Japón (Nikkei 225) 9.664525 EE.UU. (S&P 500) 14.200866 Alemania (DAX) 17.265559 EE.UU. (NASDAQ) 18.626130 Reino Unido (FTSE 100) 23.422122 EE.UU. (Russell 100) 24.619089 EE.UU. (DJIA) 25.291417 México (IPC) 40.065119 Name: final cumulative returns Benchmarks (%), dtype: float64
Correlation¶
Correlation equation¶
Pearson correlation coefficient:
$$\rho_{X,Y} = \frac {cov(X,Y)}{\sigma_X \sigma_Y} = \frac{E[(X-\mu_X)(Y-\mu_Y)]}{\sigma_X \sigma_Y}$$
$$ \sigma_p^2 = w_A^2 \sigma_A^2 \;+\; w_B^2 \sigma_B^2 \;+\; 2\,w_A w_B\,\rho_{AB}\,\sigma_A \sigma_B $$
Stocks¶
functions.plot_interactive_heatmap_correlation(daily_returns, triangle='half')
functions.plot_extreme_correlations(daily_returns, num_pairs=10)
================================================================================ REGRESSION & VOLATILITY SUMMARY TABLE ================================================================================
| Correlation (r) | R-Squared | p-value | Slope (Beta) | Std Error | Intercept (Alpha) | Vol_GOOGL | Vol_GOOG | Significant? | Vol_VOOG | Vol_SPYG | Vol_VUG | Vol_QQQM | Vol_VYM | Vol_VTV | Vol_VGT | Vol_CME | Vol_ASML | Vol_NVDA | Vol_KO | Vol_TSM | Vol_SOXX | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Pair | ||||||||||||||||||||||
| GOOGL vs GOOG | 0.9979 | 0.9958 | 0.0000 | 0.9850 | 0.0030 | 0.0000 | 0.0191 | 0.0189 | Yes | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VOOG vs SPYG | 0.9974 | 0.9948 | 0.0000 | 1.0013 | 0.0034 | 0.0000 | nan | nan | Yes | 0.0132 | 0.0132 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VUG vs VOOG | 0.9910 | 0.9821 | 0.0000 | 0.9841 | 0.0062 | 0.0001 | nan | nan | Yes | 0.0132 | nan | 0.0132 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VUG vs SPYG | 0.9884 | 0.9770 | 0.0000 | 0.9854 | 0.0071 | 0.0001 | nan | nan | Yes | nan | 0.0132 | 0.0132 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| SPYG vs QQQM | 0.9866 | 0.9733 | 0.0000 | 0.9926 | 0.0077 | -0.0001 | nan | nan | Yes | nan | 0.0132 | nan | 0.0133 | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VUG vs QQQM | 0.9863 | 0.9729 | 0.0000 | 0.9894 | 0.0077 | -0.0000 | nan | nan | Yes | nan | nan | 0.0132 | 0.0133 | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VOOG vs QQQM | 0.9862 | 0.9725 | 0.0000 | 0.9962 | 0.0078 | -0.0001 | nan | nan | Yes | 0.0132 | nan | nan | 0.0133 | nan | nan | nan | nan | nan | nan | nan | nan | nan |
| VYM vs VTV | 0.9839 | 0.9680 | 0.0000 | 0.9611 | 0.0082 | -0.0000 | nan | nan | Yes | nan | nan | nan | nan | 0.0086 | 0.0084 | nan | nan | nan | nan | nan | nan | nan |
| VGT vs QQQM | 0.9759 | 0.9524 | 0.0000 | 0.8100 | 0.0085 | 0.0001 | nan | nan | Yes | nan | nan | nan | 0.0133 | nan | nan | 0.0160 | nan | nan | nan | nan | nan | nan |
| VGT vs SPYG | 0.9720 | 0.9449 | 0.0000 | 0.8019 | 0.0091 | 0.0002 | nan | nan | Yes | nan | 0.0132 | nan | nan | nan | nan | 0.0160 | nan | nan | nan | nan | nan | nan |
| CME vs ASML | -0.2532 | 0.0641 | 0.0000 | -0.6253 | 0.1120 | 0.0013 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | 0.0272 | nan | nan | nan | nan |
| NVDA vs KO | -0.2519 | 0.0634 | 0.0000 | -0.0781 | 0.0141 | 0.0006 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | nan | nan | 0.0318 | 0.0099 | nan | nan |
| TSM vs KO | -0.2442 | 0.0596 | 0.0000 | -0.0939 | 0.0175 | 0.0007 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 0.0099 | 0.0257 | nan |
| SOXX vs CME | -0.2406 | 0.0579 | 0.0000 | -0.1117 | 0.0211 | 0.0009 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | nan | nan | nan | nan | 0.0238 |
| TSM vs CME | -0.2195 | 0.0482 | 0.0000 | -0.0944 | 0.0197 | 0.0010 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | nan | nan | nan | 0.0257 | nan |
| VGT vs CME | -0.1855 | 0.0344 | 0.0001 | -0.1279 | 0.0318 | 0.0009 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | 0.0160 | 0.0110 | nan | nan | nan | nan | nan |
| NVDA vs CME | -0.1810 | 0.0328 | 0.0001 | -0.0628 | 0.0160 | 0.0009 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | nan | 0.0318 | nan | nan | nan |
| SOXX vs KO | -0.1750 | 0.0306 | 0.0002 | -0.0726 | 0.0192 | 0.0005 | nan | nan | Yes | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 0.0099 | nan | 0.0238 |
| GOOGL vs CME | -0.1610 | 0.0259 | 0.0005 | -0.0930 | 0.0267 | 0.0009 | 0.0191 | nan | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | nan | nan | nan | nan | nan |
| GOOG vs CME | -0.1578 | 0.0249 | 0.0007 | -0.0924 | 0.0271 | 0.0009 | nan | 0.0189 | Yes | nan | nan | nan | nan | nan | nan | nan | 0.0110 | nan | nan | nan | nan | nan |
--------------------------------------------------
INTERPRETATION GUIDE:
--------------------------------------------------
• p-value Significance: Probability the correlation happened by chance.
Small values (e.g., < 1.42e-05) mean the relationship is mathematically solid.
• Significance?: 'Yes' if p_value < 0.05.
• Beta (Slope): The magnitude. A slope of 1.2 means for every 1% change in Stock A,
Stock B tends to move 1.2%.
• Alpha (Intercept): The 'excess' return of Stock B if Stock A's return was zero.
• Low Standard Error: Relationship is tight; Beta is a reliable predictor.
• High Standard Error: Lots of 'noise'; Beta is less reliable for hedging/pairs trading.
• Volatility (Vol):
- High Vol + High Corr: High-octane comovers.
- Low Vol + High Corr: Stable 'pairs trading' candidates.
- Interpretation: If Vol Stock B > Vol Stock A, Beta will naturally be higher.
Check this to see if Beta is driven by correlation or just swing scale.
Indices & Portfolio¶
functions.plot_interactive_heatmap_correlation(merge_daily_returns, triangle='half')
functions.plot_extreme_correlations(merge_daily_returns, num_pairs=10)
================================================================================ REGRESSION & VOLATILITY SUMMARY TABLE ================================================================================
| Correlation (r) | R-Squared | p-value | Slope (Beta) | Std Error | Intercept (Alpha) | Vol_Reino Unido (FTSE 100) | Vol_EE.UU. (Russell 100) | Significant? | Vol_México (IPC) | Vol_Initial Portfolio | Vol_EE.UU. (S&P 500) | Vol_EE.UU. (DJIA) | Vol_EE.UU. (NASDAQ) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Pair | ||||||||||||||
| Reino Unido (FTSE 100) vs EE.UU. (Russell 100) | 0.9990 | 0.9981 | 0.0000 | 0.9888 | 0.0023 | 0.0000 | 0.0109 | 0.0108 | Yes | nan | nan | nan | nan | nan |
| México (IPC) vs EE.UU. (Russell 100) | 0.9685 | 0.9381 | 0.0000 | 0.7283 | 0.0100 | -0.0001 | nan | 0.0108 | Yes | 0.0144 | nan | nan | nan | nan |
| Reino Unido (FTSE 100) vs México (IPC) | 0.9656 | 0.9323 | 0.0000 | 1.2709 | 0.0182 | 0.0002 | 0.0109 | nan | Yes | 0.0144 | nan | nan | nan | nan |
| Initial Portfolio vs México (IPC) | 0.9473 | 0.8974 | 0.0000 | 0.7949 | 0.0143 | -0.0005 | nan | nan | Yes | 0.0144 | 0.0172 | nan | nan | nan |
| Reino Unido (FTSE 100) vs EE.UU. (S&P 500) | 0.9064 | 0.8216 | 0.0000 | 0.7962 | 0.0198 | -0.0001 | 0.0109 | nan | Yes | nan | nan | 0.0096 | nan | nan |
| EE.UU. (Russell 100) vs EE.UU. (S&P 500) | 0.9004 | 0.8107 | 0.0000 | 0.7991 | 0.0206 | -0.0001 | nan | 0.0108 | Yes | nan | nan | 0.0096 | nan | nan |
| Initial Portfolio vs EE.UU. (Russell 100) | 0.8918 | 0.7953 | 0.0000 | 0.5627 | 0.0152 | -0.0004 | nan | 0.0108 | Yes | nan | 0.0172 | nan | nan | nan |
| Initial Portfolio vs Reino Unido (FTSE 100) | 0.8840 | 0.7815 | 0.0000 | 0.5636 | 0.0159 | -0.0004 | 0.0109 | nan | Yes | nan | 0.0172 | nan | nan | nan |
| México (IPC) vs EE.UU. (S&P 500) | 0.7916 | 0.6266 | 0.0000 | 0.5283 | 0.0217 | -0.0001 | nan | nan | Yes | 0.0144 | nan | 0.0096 | nan | nan |
| EE.UU. (DJIA) vs EE.UU. (NASDAQ) | 0.7148 | 0.5110 | 0.0000 | 0.5083 | 0.0265 | 0.0002 | nan | nan | Yes | nan | nan | nan | 0.0103 | 0.0074 |
--------------------------------------------------
INTERPRETATION GUIDE:
--------------------------------------------------
• p-value Significance: Probability the correlation happened by chance.
Small values (e.g., < 1.42e-05) mean the relationship is mathematically solid.
• Significance?: 'Yes' if p_value < 0.05.
• Beta (Slope): The magnitude. A slope of 1.2 means for every 1% change in Stock A,
Stock B tends to move 1.2%.
• Alpha (Intercept): The 'excess' return of Stock B if Stock A's return was zero.
• Low Standard Error: Relationship is tight; Beta is a reliable predictor.
• High Standard Error: Lots of 'noise'; Beta is less reliable for hedging/pairs trading.
• Volatility (Vol):
- High Vol + High Corr: High-octane comovers.
- Low Vol + High Corr: Stable 'pairs trading' candidates.
- Interpretation: If Vol Stock B > Vol Stock A, Beta will naturally be higher.
Check this to see if Beta is driven by correlation or just swing scale.
Covariance¶
- La matriz de covarianza es la base para calcular la varianza del portafolio:
$$ \sigma_p^2 = w^T \Sigma w $$
donde:
- w = vector de pesos del portafolio.
- $\Sigma = matriz$ de covarianzas.
- Un gestor de portafolios busca combinaciones de activos con baja covarianza para reducir la volatilidad total manteniendo el rendimiento esperado.
- Covariance:$$ \text{Cov}(X,Y) = E[(X - \mu_X)(Y - \mu_Y)]$$
- Values:
- Positive → one rises, other rises too.
- Negative → one rises, other goes down.
- near 0 → there's no clear relationtip.
Stocks¶
# Plot ANNUALIZED variance/covariance Matrix = returns.cov() * 252
functions.plot_annualized_covariance_heatmap(daily_returns, triangle='half')
Indices & Portfolio¶
# Plot ANNUALIZED variance/covariance Matrix = returns.cov() * 252
functions.plot_annualized_covariance_heatmap(merge_daily_returns, triangle='half')
Efficient Frontier (Sharpe Ratio)¶
$$ Sharpe = \frac{R_p - R_f}{\sigma_p} = \frac{\mu_p - r_f}{\sigma_p}$$
Where:
- $R_p$: Expected Portfolio Return, $\mu$
- $R_f$: Risk Free Rate (can be 0 if ignored)
- $\sigma_p$: Portfolio Risk (StdDev)
Long-only: weights $\,w_i\in[0,1]$ y $\sum w_i=1$.
- Only purchases, no leverage (by shorting)
- Pros: Less operating risk
- Cons: Frontier less efficient than with shorts (due leverage)
Shorts allowed: weights $\,w_i\in[-1,1]$ y $\sum w_i=1 \text{ (or similar)}$.
- One can short sell and compensate with long positions
- Pros: Theoretical improvement of the Frontier with more options
- Cons:: Implied leverage, Margin requirements, Costs and Operational Risk
Stocks¶
# --- EFFICIENT FRONTIER & CAPITAL MARKET LINE ANALYSIS---
import functions # => .../functions.ipynb file attached
importlib.reload(functions) # Reloads the module
functions.run_full_frontier_analysis(rets=daily_returns,
curr_port_weights = weights_df['Weights'],
curr_port_vol = portfolio_annualized_volatility,
curr_port_ret = portfolio_annualized_return,
mean_ann = daily_returns.mean() * 252,
cov_ann = daily_returns.cov() * 252,
rf_default = risk_free,
long_only = True, # Set to True for standard long-only constraints
portfolio_value = Total_invested, #100000
yields = yields/100
)
VBox(children=(HBox(children=(FloatText(value=4.152, description='RF Rate %:'), FloatText(value=5.0, descripti…
Number of starts: To avoid local minimas. 10-25 normal (fast and robust), 50-100 (for large amount of stocks)
no_starts = 25
no_simul = 2000000
weights_optimal, vol_ret_sr_optimal = functions.efficient_froentier_sharp_ratio(
daily_returns,
[portfolio_annualized_volatility, portfolio_annualized_return],
daily_returns.mean()*252, # Simple Arithmetic Mean Annualization
daily_returns.cov()*252, #Annualized Covariance
risk_free, #Risk Free
True, # Long only = True, Short allowed = False
no_starts, # no. of starts (25 default)
no_simul, # no. of simulations
123 # seed
)
Optimizing Sharpe… Optimum Weights (%) - Tangency Portfolio Ticker AAPL 0.00 ADBE 0.00 ASML 0.00 BAC 1.45 BRK-B 0.00 CAT 2.55 CME 19.14 CRWD 0.00 DIS 0.00 GLD 39.85 GOOG 0.00 GOOGL 11.69 HD 0.00 KO 12.11 LMT 0.10 MAR 0.00 MCD 0.00 META 0.00 MSFT 0.00 NFLX 0.00 NU 0.00 NVDA 0.00 OMAB 1.50 PBR 0.00 PLTR 8.47 PSA 0.00 QQQM 0.00 QSR 0.00 QYLD 0.00 SERV 0.78 SOFI 1.23 SOXX 0.00 SPG 0.00 SPYD 0.00 SPYG 0.00 TSLA 0.00 TSM 1.13 UNH 0.00 VGT 0.00 VOO 0.00 VOOG 0.00 VOOV 0.00 VTV 0.00 VUG 0.00 VYM 0.00 Optimum Sharpe Ratio: 3.040 Expected Annual Return: 42.80% Annual Risk: 12.71% Simulating random Portfolios… Simulated Portfolio with Minimum Volatility: Volatility: 11.44% Return: 21.65% Sharpe Ratio: 1.529 Simulated Portfolio with Maximum Return: Volatility: 69.42% Return: 70.52% Sharpe Ratio: 0.956 Current Portfolio: Volatility: 26.13% Return: 38.18% Sharpe Ratio: 1.302
Positions from current to optimal¶
# Recall Total Invested
Investment = Total_invested
# Current Portolio Weights and Positions
weights_df
# Optimal Weights from Efficient Frontier
weights_optimal
# Merge DataFrames
weights_df_Optimal = pd.merge(weights_df, weights_optimal, left_index=True, right_index=True, how='outer')
# New Investment (USD)
weights_df_Optimal['New Investment usd'] = weights_df_Optimal['Optimal Weights'] * Investment
# New QTY
weights_df_Optimal['New QTY'] = weights_df_Optimal['New Investment usd'] / weights_df_Optimal['Close Price']
# Difference in QTY or buy/sell position
weights_df_Optimal['Position_to_Optimal'] = weights_df_Optimal['New QTY'] - weights_df_Optimal['Current QTY']
weights_df_Optimal = round(weights_df_Optimal, 3)
display(weights_df_Optimal)
print(f"Current Investment: $ {round(Investment, 2)}")
print(f"New Investment: $ {round(weights_df_Optimal['New Investment usd'].sum(), 2)}")
| Current QTY | Close Price | Investment | Weights | Optimal Weights | New Investment usd | New QTY | Position_to_Optimal | |
|---|---|---|---|---|---|---|---|---|
| Ticker | ||||||||
| AAPL | 17.040 | 267.26 | 4554.121 | 0.066 | 0.000 | 0.000 | 0.000 | -17.040 |
| ADBE | 0.465 | 331.56 | 154.205 | 0.002 | 0.000 | 0.000 | 0.000 | -0.465 |
| ASML | 0.264 | 1228.19 | 324.451 | 0.005 | 0.000 | 0.000 | 0.000 | -0.264 |
| BAC | 1.469 | 56.89 | 83.563 | 0.001 | 0.014 | 998.060 | 17.544 | 16.075 |
| BRK-B | 0.245 | 498.52 | 122.028 | 0.002 | 0.000 | 0.000 | 0.000 | -0.245 |
| CAT | 0.334 | 616.10 | 205.932 | 0.003 | 0.026 | 1755.209 | 2.849 | 2.515 |
| CME | 2.000 | 275.06 | 550.120 | 0.008 | 0.191 | 13174.395 | 47.896 | 45.896 |
| CRWD | 1.455 | 456.55 | 664.399 | 0.010 | 0.000 | 0.000 | 0.000 | -1.455 |
| DIS | 13.029 | 114.07 | 1486.232 | 0.022 | 0.000 | 0.000 | 0.000 | -13.029 |
| GLD | 6.449 | 408.76 | 2636.236 | 0.038 | 0.398 | 27429.449 | 67.104 | 60.655 |
| GOOG | 1.614 | 317.32 | 512.100 | 0.007 | 0.000 | 0.000 | 0.000 | -1.614 |
| GOOGL | 2.012 | 316.54 | 636.790 | 0.009 | 0.117 | 8046.430 | 25.420 | 23.408 |
| HD | 0.021 | 344.09 | 7.210 | 0.000 | 0.000 | 0.000 | 0.000 | -0.021 |
| KO | 6.227 | 67.94 | 423.063 | 0.006 | 0.121 | 8335.524 | 122.689 | 116.462 |
| LMT | 0.319 | 511.57 | 163.252 | 0.002 | 0.001 | 68.832 | 0.135 | -0.185 |
| MAR | 0.457 | 311.03 | 142.130 | 0.002 | 0.000 | 0.000 | 0.000 | -0.457 |
| MCD | 1.054 | 299.86 | 315.954 | 0.005 | 0.000 | 0.000 | 0.000 | -1.054 |
| META | 1.578 | 658.79 | 1039.452 | 0.015 | 0.000 | 0.000 | 0.000 | -1.578 |
| MSFT | 19.752 | 472.85 | 9339.751 | 0.136 | 0.000 | 0.000 | 0.000 | -19.752 |
| NFLX | 1.197 | 91.46 | 109.498 | 0.002 | 0.000 | 0.000 | 0.000 | -1.197 |
| NU | 13.623 | 17.94 | 244.388 | 0.004 | 0.000 | 0.000 | 0.000 | -13.623 |
| NVDA | 100.529 | 188.12 | 18911.482 | 0.275 | 0.000 | 0.000 | 0.000 | -100.529 |
| OMAB | 2.164 | 109.26 | 236.470 | 0.003 | 0.015 | 1032.476 | 9.450 | 7.285 |
| PBR | 87.444 | 11.74 | 1026.597 | 0.015 | 0.000 | 0.000 | 0.000 | -87.444 |
| PLTR | 21.024 | 174.04 | 3658.947 | 0.053 | 0.085 | 5830.048 | 33.498 | 12.475 |
| PSA | 0.190 | 260.90 | 49.624 | 0.001 | 0.000 | 0.000 | 0.000 | -0.190 |
| QQQM | 6.376 | 254.43 | 1622.126 | 0.024 | 0.000 | 0.000 | 0.000 | -6.376 |
| QSR | 5.255 | 66.74 | 350.734 | 0.005 | 0.000 | 0.000 | 0.000 | -5.255 |
| QYLD | 100.860 | 17.76 | 1791.281 | 0.026 | 0.000 | 0.000 | 0.000 | -100.860 |
| SERV | 3.365 | 12.68 | 42.666 | 0.001 | 0.008 | 536.888 | 42.341 | 38.976 |
| SOFI | 5.320 | 29.28 | 155.765 | 0.002 | 0.012 | 846.630 | 28.915 | 23.595 |
| SOXX | 3.046 | 318.06 | 968.755 | 0.014 | 0.000 | 0.000 | 0.000 | -3.046 |
| SPG | 0.199 | 183.11 | 36.404 | 0.001 | 0.000 | 0.000 | 0.000 | -0.199 |
| SPYD | 3.646 | 43.69 | 159.305 | 0.002 | 0.000 | 0.000 | 0.000 | -3.646 |
| SPYG | 1.019 | 107.16 | 109.187 | 0.002 | 0.000 | 0.000 | 0.000 | -1.019 |
| TSLA | 4.925 | 451.67 | 2224.304 | 0.032 | 0.000 | 0.000 | 0.000 | -4.925 |
| TSM | 10.035 | 322.25 | 3233.660 | 0.047 | 0.011 | 777.799 | 2.414 | -7.621 |
| UNH | 5.037 | 342.02 | 1722.644 | 0.025 | 0.000 | 0.000 | 0.000 | -5.037 |
| VGT | 2.019 | 757.42 | 1529.254 | 0.022 | 0.000 | 0.000 | 0.000 | -2.019 |
| VOO | 2.288 | 632.46 | 1447.284 | 0.021 | 0.000 | 0.000 | 0.000 | -2.288 |
| VOOG | 6.296 | 446.51 | 2811.387 | 0.041 | 0.000 | 0.000 | 0.000 | -6.296 |
| VOOV | 10.365 | 207.51 | 2150.748 | 0.031 | 0.000 | 0.000 | 0.000 | -10.365 |
| VTV | 0.405 | 194.65 | 78.817 | 0.001 | 0.000 | 0.000 | 0.000 | -0.405 |
| VUG | 1.008 | 488.45 | 492.502 | 0.007 | 0.000 | 0.000 | 0.000 | -1.008 |
| VYM | 2.105 | 145.82 | 306.923 | 0.004 | 0.000 | 0.000 | 0.000 | -2.105 |
Current Investment: $ 68831.74 New Investment: $ 68831.74
Including Risk Free Asset¶
Once you have the tangency portfolio 𝑤𝑡 (long-only), the fraction of your wealth 𝑋 to put into the risky portfolio is 𝑦.
Choose 𝑦 based on your target volatility, target return, or risk-aversion:
- Objective 1. Target Volatility:
$$ 𝑦 = \frac{𝜎_𝑝}{𝜎_𝑡} $$
- Objective 2. Target Return:
$$ 𝑦 = \frac{𝐸_𝑝 − 𝑅_𝑓}{𝐸[𝑅_𝑡] − 𝑅_𝑓} $$
- Objective 3. Mean-Variance Utility (Expected Utility Funcion):
$$ U = E[R_p] - \frac{1}{2} \gamma \sigma_p^{2} $$
$$ 𝑦* = \frac{𝐸[𝑅_𝑡] − 𝑅_𝑓}{𝛾 𝜎_𝑡^{2}}$$
U : “Utility” — a scalar number representing the investor’s satisfaction from a portfolio. Its a parabolic function, higher gama the steeper the curve, thus more return expected for unit of risk.
𝛾 > 0: Risk aversion coefficient, a measure of how strongly the investor dislikes risk. Penalizes risk. Higher->more conservative.
Finally: invest 𝑦𝑋 in 𝑤_𝑡 and (1−𝑦)𝑋 in the risk-free asset, enforcing 0≤𝑦≤1 for long-only/no-borrowing.
# Objective 1. You want a target portfolio volatility 𝜎𝑝 :
# 𝜎𝑝 (target volatility):
# 90% of the Tangency portfolio volatility (can be any value)
target_volatility = vol_ret_sr_optimal[0] * 0.90
# The fraction of total wealth X invested in the tangency (risky) portfolio
y = target_volatility / vol_ret_sr_optimal[0]
print(f"Target Volatility: {target_volatility:.2%}")
print(f"The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: {y:.2%}, that is ${y*Total_invested:,.2f}usd")
print(f"The fraction invested in the risk-free asset (1-y) is: {1-y:.2%}, that is ${(1-y)*Total_invested:,.2f}usd")
ER_p = risk_free + y*(vol_ret_sr_optimal[1] - risk_free)
print(f"Expected Return E[Rp] = {ER_p:.2%}")
print(f"Sharpe Ratio = {(ER_p - risk_free) / target_volatility:.2f} ")
Target Volatility: 11.44% The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: 90.00%, that is $61,948.57usd The fraction invested in the risk-free asset (1-y) is: 10.00%, that is $6,883.17usd Expected Return E[Rp] = 38.94% Sharpe Ratio = 3.04
# Objective 2. You want a target expected return Ep
# Ep (target Expected Return):
# 90% of the Tangency portfolio return (can be any value)
target_return = vol_ret_sr_optimal[1] * 0.90
# The fraction of total wealth X invested in the tangency (risky) portfolio
y = (target_return - risk_free) / (vol_ret_sr_optimal[1] - risk_free)
print(f"Target Expected Return: {target_return:.2%}")
print(f"The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: {y:.2%}, that is ${y*Total_invested:,.2f}usd")
print(f"The fraction invested in the risk-free asset (1-y) is: {1-y:.2%}, that is ${(1-y)*Total_invested:,.2f}usd")
sigma_p = y * vol_ret_sr_optimal[0]
print(f"Volatility: {sigma_p:.2%}")
print(f"Sharpe Ratio = {(target_return - risk_free)/sigma_p:.2f}")
Target Expected Return: 38.52% The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: 88.93%, that is $61,209.13usd The fraction invested in the risk-free asset (1-y) is: 11.07%, that is $7,622.61usd Volatility: 11.31% Sharpe Ratio = 3.04
# Objective 3. You maximize mean–variance Utility (risk aversion 𝛾)
# Gamma (risk aversion coefficient) :
# If 𝛾 is large, the investor is very risk averse — even small increases in variance are penalized heavily.
# → They prefer portfolios with lower volatility, even if returns are modest.
# If 𝛾 is small, the investor is risk tolerant (or aggressive) — they are willing to accept more variance for more expected return.
gamma = 4
# a) Allowing borrowing (short)
# The fraction of total wealth X invested in the tangency (risky) portfolio
y_star = (vol_ret_sr_optimal[1] - risk_free) / (gamma * (vol_ret_sr_optimal[0]**2))
print("a) Allowing borrowing (short in risk asset):")
print(f"Mean-variance risk aversion coefficient: {gamma}")
print(f"The fraction of total wealth X invested in the tangency (risky) portfolio y* is: {y_star:.2%}, that is ${y_star * Total_invested:,.2f}usd")
print(f"The fraction invested in the risk-free asset (1-y*) is: {1-y:.2%}, that is ${(1-y_star) * Total_invested:,.2f}usd")
# E[R_p] = Rf + y* (E[Rt] - Rf)
ER_p = risk_free + (y_star * (vol_ret_sr_optimal[1] - risk_free))
sigma_p = y_star * vol_ret_sr_optimal[0]
print(f"Expected Return E[Rp] = {ER_p:.2%}")
print(f"Volatility sigma_p = {sigma_p:.2%}")
print(f"Sharpe Ratio = {(ER_p - risk_free)/sigma_p:.2}")
# b) For long-only, no-borrowing constraint: set 𝑦=min(1,max(0,𝑦∗)).
y = min(1, max(0, y_star))
print("\nb) For long-only, no-borrowing constraint:")
print(f"Mean-variance risk aversion coefficient: {gamma}")
print(f"The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: {y:.2%}, that is ${y*Total_invested:,.2f}usd")
print(f"The fraction invested in the risk-free asset (1-y) is: {1-y:.2%}, that is ${(1-y)*Total_invested:,.2f}usd")
ER_p = risk_free + (y * (vol_ret_sr_optimal[1] - risk_free))
sigma_p = y * vol_ret_sr_optimal[0]
print(f"Expected Return E[Rp] = {ER_p:.2%}")
print(f"Volatility sigma_p = {sigma_p:.2%}")
print(f"Sharpe Ratio = {(ER_p - risk_free)/sigma_p:.2}")
a) Allowing borrowing (short in risk asset): Mean-variance risk aversion coefficient: 4 The fraction of total wealth X invested in the tangency (risky) portfolio y* is: 597.81%, that is $411,484.33usd The fraction invested in the risk-free asset (1-y*) is: 11.07%, that is $-342,652.59usd Expected Return E[Rp] = 235.20% Volatility sigma_p = 76.00% Sharpe Ratio = 3.0 b) For long-only, no-borrowing constraint: Mean-variance risk aversion coefficient: 4 The fraction of total wealth X invested in the tangency (risky) portfolio 'y' is: 100.00%, that is $68,831.74usd The fraction invested in the risk-free asset (1-y) is: 0.00%, that is $0.00usd Expected Return E[Rp] = 42.80% Volatility sigma_p = 12.71% Sharpe Ratio = 3.0
The tangency portfolio has an enormously high risk-adjusted return — i.e., its Sharpe ratio(𝐸[𝑅𝑡]−𝑅𝑓)/𝜎𝑡=0.405/0.135=3.0(E[Rt]−Rf)/σt=0.405/0.135=3.0 is extremely high.
gamma = 4 That’s a moderate risk aversion level. Even with moderate aversion, such a high Sharpe ratio pushes you toward aggressive leverage.
The tangency portfolio has an extremely high Sharpe (3.0). With moderate risk aversion 𝛾=4, the utility-maximizing solution is to lever the tangency portfolio heavily (556% of wealth) because the reward-to-risk tradeoff is so favorable.
Indices & Portfolio¶
# ANNUAL DIVIDEND YIELDS
yields_merge = functions.plot_annual_dividnd_yields(merge_daily_returns)
HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404: HTTP Error 404:
Annual Dividend Yields from yf 'dividendYield' (%):
EE.UU. (S&P 500) 0 EE.UU. (NASDAQ) 0 EE.UU. (DJIA) 0 EE.UU. (Russell 100) 0 México (IPC) 0 Japón (Nikkei 225) 0 Alemania (DAX) 0 Reino Unido (FTSE 100) 0 Initial Portfolio 0 Name: dividendYield, dtype: int64
# --- EFFICIENT FRONTIER & CAPITAL MARKET LINE ANALYSIS---
import functions # => .../functions.ipynb file attached
importlib.reload(functions) # Reloads the module
functions.run_full_frontier_analysis(rets = merge_daily_returns,
curr_port_weights = weights_df['Weights'],
curr_port_vol = portfolio_annualized_volatility,
curr_port_ret = portfolio_annualized_return,
mean_ann = merge_daily_returns.mean() * 252,
cov_ann = merge_daily_returns.cov() * 252,
rf_default = risk_free,
long_only = True, # Set to True for standard long-only constraints
portfolio_value = Total_invested, #100000
yields = yields_merge/100
)
VBox(children=(HBox(children=(FloatText(value=4.152, description='RF Rate %:'), FloatText(value=5.0, descripti…
weights_optimal, vol_ret_sr_optimal = functions.efficient_froentier_sharp_ratio(
merge_daily_returns,
[portfolio_annualized_volatility, portfolio_annualized_return],
merge_daily_returns.mean()*252, # Simple Arithmetic Mean Annualization
merge_daily_returns.cov()*252, #Annualized Covariance
risk_free, #Risk Free
True, # Long only = True, Short allowed = False
no_starts, # no. of starts (25 default)
no_simul, # no. of simulations
123 # seed
)
Optimizing Sharpe… Optimum Weights (%) - Tangency Portfolio EE.UU. (S&P 500) 0.00 EE.UU. (NASDAQ) 37.73 EE.UU. (DJIA) 7.24 EE.UU. (Russell 100) 0.00 México (IPC) 0.00 Japón (Nikkei 225) 0.00 Alemania (DAX) 1.48 Reino Unido (FTSE 100) 0.00 Initial Portfolio 53.55 Optimum Sharpe Ratio: 1.715 Expected Annual Return: 32.19% Annual Risk: 16.35% Simulating random Portfolios… Simulated Portfolio with Minimum Volatility: Volatility: 10.05% Return: 12.42% Sharpe Ratio: 0.823 Simulated Portfolio with Maximum Return: Volatility: 24.61% Return: 43.19% Sharpe Ratio: 1.586 Current Portfolio: Volatility: 26.13% Return: 38.18% Sharpe Ratio: 1.302
**TO-DO: Include GUI and "make your own portfolio" with a variety of Stocks to choose.¶
(see Frontera Eficiente for GUI)
Indicators: alpha, Beta, R^2, SR, Sortino, VaR, CVaR - vs with Benchmark¶
| Métrica | Valor Óptimo / Bueno | Interpretación |
|---|---|---|
| Alpha (α) | > 0 (ideal: +2% a +10% anual) | Exceso de retorno sobre lo esperado por el CAPM; positivo indica valor agregado. |
| Beta (β) | ≈ 1 (mercado), <1 defensivo, >1 agresivo | Sensibilidad al mercado; >1 = más volátil, <1 = más estable. |
| R² (correlación) | > 0.8 (80% o más) | El portafolio se mueve casi igual que el benchmark (muy “indexado”). |
| R² (correlación) | 0.6 – 0.8 | Buena relación con el mercado, pero con diferencias notables. |
| R² (correlación) | < 0.30 | El portafolio se comporta muy distinto al mercado (alta independencia). |
| µ (Retorno anual) | > 8% estable, > 15% agresivo | Rendimiento esperado anualizado del portafolio. |
| σ (Volatilidad anual) | 10%–20% moderado, <10% defensivo, >25% muy riesgoso | Mide el riesgo total (desviación estándar). |
| Sharpe Ratio | > 1 bueno, > 1.5 muy bueno, > 2 excelente | Retorno ajustado por riesgo total. |
| Sortino Ratio | > 2 excelente | Retorno ajustado por riesgo a la baja (mejor si >> Sharpe). |
| VaR (95%, 1d) | < 2% | Pérdida máxima esperada en un día con 95% de confianza. |
| CVaR (95%, 1d) | < 3% | Pérdida promedio en los peores días (cola izquierda de la distribución). |
#recall
display(benchmark_indices)
{'EE.UU. (S&P 500)': '^GSPC',
'EE.UU. (NASDAQ)': '^IXIC',
'EE.UU. (DJIA)': '^DJI',
'EE.UU. (Russell 100)': '^RUI',
'México (IPC)': '^MXX',
'Japón (Nikkei 225)': '^N225',
'Alemania (DAX)': '^GDAXI',
'Reino Unido (FTSE 100)': '^FTSE'}
# Choose a benchmark to compare for alpha, beta, R2...
index_benchmark = 0
functions.indicators(tickers, start_date, today, benchmark_indices, index_benchmark,
risk_free, portfolio_weights=pd.Series(weights_df['Weights']), no_starts=no_starts)
** TO-DO: EN Metricas Anualizadas incluir de forma automatica los otros 6 benchmarks¶
TO-DO: CAPM (en archivo de Indicadores)¶
CAPM (Capital Asset Pricing Model)¶
CAPM Equation:
$$ r_{i} = r_{f} + \beta_{im}*(r_{m}-r_{f}) $$
where :
$r_i = $ Expected Asset Return
$r_f = $ Risk-free asset Return
$\beta_{i,m} = $ Asset Beta w.r.t market
$r_m = $ Market Return
Risk-free is the minimum Return an Investor can accept.
Difference between $r_{m}$ and $r_{f}$ is the Premium that the investor recieves by taking the risk (equity risk Premium).
$\beta$ measures the cuantity of Risk of an asset with respect to the Market.
Asset Beta ( $\beta$ )¶
$$\beta = \frac{\text{Cov(}r_A, r_m\text{)}}{Var(r_m)} = \frac{\sigma_{A,m}}{\sigma_m^2} = \frac{ρ_{A,m} σ_m σ_A}{\sigma_m^2} = \frac{ρ_{A,m}σ_A}{\sigma_m}$$